() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaLoadResult(result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+ /**
+ * Loads and optionally starts playback of a new queue of media items.
+ *
+ * @param items Array of items to load, in the order that they should be played. Must not be
+ * {@code null} or empty.
+ * @param startIndex The array index of the item in the {@code items} array that should be
+ * played first (i.e., it will become the currentItem).If {@code repeatMode}
+ * is {@link MediaStatus#REPEAT_MODE_REPEAT_OFF} playback will end when the
+ * last item in the array is played.
+ *
+ * This may be useful for continuation scenarios where the user was already
+ * using the sender application and in the middle decides to cast. This lets
+ * the sender application avoid mapping between the local and remote queue
+ * positions and/or avoid issuing an extra request to update the queue.
+ *
+ * This value must be less than the length of {@code items}.
+ * @param repeatMode The repeat playback mode for the queue. One of
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_OFF},
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL},
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_SINGLE} and
+ * {@link MediaStatus#REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE}.
+ * @param customData Custom application-specific data to pass along with the request, may be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueLoad(final MediaQueueItem[] items, final int startIndex, final int repeatMode,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "queueLoad");
+ checkConnectivity();
+ if (items == null || items.length == 0) {
+ return;
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to queue one or more videos with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueLoad(mApiClient, items, startIndex, repeatMode, customData)
+ .setResultCallback(new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_LOAD,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Inserts a list of new media items into the queue.
+ *
+ * @param itemsToInsert List of items to insert into the queue, in the order that they should be
+ * played. The itemId field of the items should be unassigned or the
+ * request will fail with an INVALID_PARAMS error. Must not be {@code null}
+ * or empty.
+ * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
+ * list. If the value is {@link MediaQueueItem#INVALID_ITEM_ID} or
+ * invalid, the inserted list will be appended to the end of the
+ * queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueInsertItems(final MediaQueueItem[] itemsToInsert, final int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "queueInsertItems");
+ checkConnectivity();
+ if (itemsToInsert == null || itemsToInsert.length == 0) {
+ throw new IllegalArgumentException("items cannot be empty or null");
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to insert into queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueInsertItems(mApiClient, itemsToInsert, insertBeforeItemId, customData)
+ .setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(
+ QUEUE_OPERATION_INSERT_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Updates properties of a subset of the existing items in the media queue.
+ *
+ * @param itemsToUpdate List of queue items to be updated. The items will retain the existing
+ * order and will be fully replaced with the ones provided, including the
+ * media information. Any other items currently in the queue will remain
+ * unchanged. The tracks information can not change once the item is loaded
+ * (if the item is the currentItem). If any of the items does not exist it
+ * will be ignored.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueUpdateItems(final MediaQueueItem[] itemsToUpdate, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueUpdateItems(mApiClient, itemsToUpdate, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ LOGD(TAG, "queueUpdateItems() " + result.getStatus() + result.getStatus()
+ .isSuccess());
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_UPDATE_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Plays the item with {@code itemId} in the queue.
+ *
+ * If {@code itemId} is not found in the queue, this method will report success without sending
+ * a request to the receiver.
+ *
+ * @param itemId The ID of the item to which to jump.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueJumpToItem(int itemId, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ checkConnectivity();
+ if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException("itemId is not valid");
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to jump in a queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueJumpToItem(mApiClient, itemId, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_JUMP,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes a list of items from the queue. If the remaining queue is empty, the media session
+ * will be terminated.
+ *
+ * @param itemIdsToRemove The list of media item IDs to remove. Must not be {@code null} or
+ * empty.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueRemoveItems(final int[] itemIdsToRemove, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ LOGD(TAG, "queueRemoveItems");
+ checkConnectivity();
+ if (itemIdsToRemove == null || itemIdsToRemove.length == 0) {
+ throw new IllegalArgumentException("itemIds cannot be empty or null");
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to remove items from queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueRemoveItems(mApiClient, itemIdsToRemove, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes the item with {@code itemId} from the queue.
+ *
+ * If {@code itemId} is not found in the queue, this method will silently return without sending
+ * a request to the receiver. A {@code itemId} may not be in the queue because it wasn't
+ * originally in the queue, or it was removed by another sender.
+ *
+ * @param itemId The ID of the item to be removed.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueRemoveItem(final int itemId, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ LOGD(TAG, "queueRemoveItem");
+ checkConnectivity();
+ if (itemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException("itemId is invalid");
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to remove an item from queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueRemoveItem(mApiClient, itemId, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REMOVE_ITEM,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Reorder a list of media items in the queue.
+ *
+ * @param itemIdsToReorder The list of media item IDs to reorder, in the new order. Any other
+ * items currently in the queue will maintain their existing order. The
+ * list will be inserted just before the item specified by
+ * {@code insertBeforeItemId}, or at the end of the queue if
+ * {@code insertBeforeItemId} is {@link MediaQueueItem#INVALID_ITEM_ID}.
+ *
+ * For example:
+ *
+ * If insertBeforeItemId is not specified
+ * Existing queue: "A","D","G","H","B","E"
+ * itemIds: "D","H","B"
+ * New Order: "A","G","E","D","H","B"
+ *
+ * If insertBeforeItemId is "A"
+ * Existing queue: "A","D","G","H","B"
+ * itemIds: "D","H","B"
+ * New Order: "D","H","B","A","G","E"
+ *
+ * If insertBeforeItemId is "G"
+ * Existing queue: "A","D","G","H","B"
+ * itemIds: "D","H","B"
+ * New Order: "A","D","H","B","G","E"
+ *
+ * If any of the items does not exist it will be ignored.
+ * Must not be {@code null} or empty.
+ * @param insertBeforeItemId ID of the item that will be located immediately after the reordered
+ * list. If set to {@link MediaQueueItem#INVALID_ITEM_ID}, the
+ * reordered list will be appended at the end of the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueReorderItems(final int[] itemIdsToReorder, final int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException,
+ IllegalArgumentException {
+ LOGD(TAG, "queueReorderItems");
+ checkConnectivity();
+ if (itemIdsToReorder == null || itemIdsToReorder.length == 0) {
+ throw new IllegalArgumentException("itemIdsToReorder cannot be empty or null");
+ }
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to reorder items in a queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueReorderItems(mApiClient, itemIdsToReorder, insertBeforeItemId, customData)
+ .setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_REORDER,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Moves the item with {@code itemId} to a new position in the queue.
+ *
+ * If {@code itemId} is not found in the queue, either because it wasn't there originally or it
+ * was removed by another sender before calling this function, this function will silently
+ * return without sending a request to the receiver.
+ *
+ * @param itemId The ID of the item to be moved.
+ * @param newIndex The new index of the item. If the value is negative, an error will be
+ * returned. If the value is out of bounds, or becomes out of bounds because the
+ * queue was shortened by another sender while this request is in progress, the
+ * item will be moved to the end of the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueMoveItemToNewIndex(int itemId, int newIndex, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ mRemoteMediaPlayer
+ .queueMoveItemToNewIndex(mApiClient, itemId, newIndex, customData)
+ .setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_MOVE,
+ result.getStatus().getStatusCode());;
+ }
+ }
+ });
+ }
+
+ /**
+ * Appends a new media item to the end of the queue.
+ *
+ * @param item The item to append. Must not be {@code null}.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueAppendItem(MediaQueueItem item, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ mRemoteMediaPlayer
+ .queueAppendItem(mApiClient, item, customData)
+ .setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_APPEND,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Jumps to the next item in the queue.
+ *
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueNext(final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueNext(mApiClient, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_NEXT,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Jumps to the previous item in the queue.
+ *
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queuePrev(final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queuePrev(mApiClient, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_PREV,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Inserts an item in the queue and starts the playback of that newly inserted item. It is
+ * assumed that we are inserting before the "current item"
+ *
+ * @param item The item to be inserted
+ * @param insertBeforeItemId ID of the item that will be located immediately after the inserted
+ * and is assumed to be the "current item"
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws IllegalArgumentException
+ */
+ public void queueInsertBeforeCurrentAndPlay(MediaQueueItem item, int insertBeforeItemId,
+ final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to insert into queue with no active media session");
+ throw new NoConnectionException();
+ }
+ if (item == null || insertBeforeItemId == MediaQueueItem.INVALID_ITEM_ID) {
+ throw new IllegalArgumentException(
+ "item cannot be empty or insertBeforeItemId cannot be invalid");
+ }
+ mRemoteMediaPlayer.queueInsertItems(mApiClient, new MediaQueueItem[]{item},
+ insertBeforeItemId, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (result.getStatus().isSuccess()) {
+
+ try {
+ queuePrev(customData);
+ } catch (TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ LOGE(TAG, "queuePrev() Failed to skip to previous", e);
+ }
+ }
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_INSERT_ITEMS,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the repeat mode of the queue.
+ *
+ * @param repeatMode The repeat playback mode for the queue.
+ * @param customData Custom application-specific data to pass along with the request. May be
+ * {@code null}.
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void queueSetRepeatMode(final int repeatMode, final JSONObject customData)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to update the queue with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer
+ .queueSetRepeatMode(mApiClient, repeatMode, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ LOGD(TAG, "Failed with status: " + result.getStatus());
+ }
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueOperationResult(QUEUE_OPERATION_SET_REPEAT,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Plays the loaded media.
+ *
+ * @param position Where to start the playback. Units is milliseconds.
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ LOGD(TAG, "attempting to play media at position " + position + " seconds");
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to play a video with no active media session");
+ throw new NoConnectionException();
+ }
+ seekAndPlay(position);
+ }
+
+ /**
+ * Resumes the playback from where it was left (can be the beginning).
+ *
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "play(customData)");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to play a video with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer.play(mApiClient, customData)
+ .setResultCallback(new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_to_play,
+ result.getStatus().getStatusCode());
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Resumes the playback from where it was left (can be the beginning).
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void play() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ play(null);
+ }
+
+ /**
+ * Stops the playback of media/stream
+ *
+ * @param customData Optional {@link JSONObject}
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void stop(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "stop()");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to stop a stream with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer.stop(mApiClient, customData).setResultCallback(
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_to_stop,
+ result.getStatus().getStatusCode());
+ }
+ }
+
+ }
+ );
+ }
+
+ /**
+ * Stops the playback of media/stream
+ *
+ * @throws CastException
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ public void stop() throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException {
+ stop(null);
+ }
+
+ /**
+ * Pauses the playback.
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void pause() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ pause(null);
+ }
+
+ /**
+ * Pauses the playback.
+ *
+ * @param customData Optional {@link JSONObject} data to be passed to the cast device
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void pause(JSONObject customData) throws
+ TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "attempting to pause media");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to pause a video with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer.pause(mApiClient, customData)
+ .setResultCallback(new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_to_pause,
+ result.getStatus().getStatusCode());
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Seeks to the given point without changing the state of the player, i.e. after seek is
+ * completed, it resumes what it was doing before the start of seek.
+ *
+ * @param position in milliseconds
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void seek(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ LOGD(TAG, "attempting to seek media");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to seek a video with no active media session");
+ throw new NoConnectionException();
+ }
+ mRemoteMediaPlayer.seek(mApiClient,
+ position,
+ RemoteMediaPlayer.RESUME_STATE_UNCHANGED).
+ setResultCallback(new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_seek, result.getStatus().getStatusCode());
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Seeks to the given point and starts playback regardless of the starting state.
+ *
+ * @param position in milliseconds
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void seekAndPlay(int position) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ LOGD(TAG, "attempting to seek media");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ LOGE(TAG, "Trying to seekAndPlay a video with no active media session");
+ throw new NoConnectionException();
+ }
+ ResultCallback resultCallback =
+ new ResultCallback() {
+
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_seek, result.getStatus().getStatusCode());
+ }
+ }
+
+ };
+ mRemoteMediaPlayer.seek(mApiClient,
+ position,
+ RemoteMediaPlayer.RESUME_STATE_PLAY).setResultCallback(resultCallback);
+ }
+
+ /**
+ * Toggles the playback of the movie.
+ *
+ * @throws CastException
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ public void togglePlayback() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ checkConnectivity();
+ boolean isPlaying = isRemoteMediaPlaying();
+ if (isPlaying) {
+ pause();
+ } else {
+ if (mState == MediaStatus.PLAYER_STATE_IDLE
+ && mIdleReason == MediaStatus.IDLE_REASON_FINISHED) {
+ loadMedia(getRemoteMediaInformation(), true, 0);
+ } else {
+ play();
+ }
+ }
+ }
+
+ private void attachMediaChannel() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ LOGD(TAG, "attachMediaChannel()");
+ checkConnectivity();
+ if (mRemoteMediaPlayer == null) {
+ mRemoteMediaPlayer = new RemoteMediaPlayer();
+
+ mRemoteMediaPlayer.setOnStatusUpdatedListener(
+ new RemoteMediaPlayer.OnStatusUpdatedListener() {
+
+ @Override
+ public void onStatusUpdated() {
+ LOGD(TAG, "RemoteMediaPlayer::onStatusUpdated() is reached");
+ VideoCastManager.this.onRemoteMediaPlayerStatusUpdated();
+ }
+ }
+ );
+
+ mRemoteMediaPlayer.setOnPreloadStatusUpdatedListener(
+ new RemoteMediaPlayer.OnPreloadStatusUpdatedListener() {
+
+ @Override
+ public void onPreloadStatusUpdated() {
+ LOGD(TAG,
+ "RemoteMediaPlayer::onPreloadStatusUpdated() is "
+ + "reached");
+ VideoCastManager.this.onRemoteMediaPreloadStatusUpdated();
+ }
+ });
+
+
+ mRemoteMediaPlayer.setOnMetadataUpdatedListener(
+ new RemoteMediaPlayer.OnMetadataUpdatedListener() {
+ @Override
+ public void onMetadataUpdated() {
+ LOGD(TAG, "RemoteMediaPlayer::onMetadataUpdated() is reached");
+ VideoCastManager.this.onRemoteMediaPlayerMetadataUpdated();
+ }
+ }
+ );
+
+ mRemoteMediaPlayer.setOnQueueStatusUpdatedListener(
+ new RemoteMediaPlayer.OnQueueStatusUpdatedListener() {
+
+ @Override
+ public void onQueueStatusUpdated() {
+ LOGD(TAG,
+ "RemoteMediaPlayer::onQueueStatusUpdated() is "
+ + "reached");
+ mMediaStatus = mRemoteMediaPlayer.getMediaStatus();
+ if (mMediaStatus != null
+ && mMediaStatus.getQueueItems() != null) {
+ List queueItems = mMediaStatus
+ .getQueueItems();
+ int itemId = mMediaStatus.getCurrentItemId();
+ MediaQueueItem item = mMediaStatus
+ .getQueueItemById(itemId);
+ int repeatMode = mMediaStatus.getQueueRepeatMode();
+ boolean shuffle = false;
+ onQueueUpdated(queueItems, item, repeatMode, shuffle);
+ } else {
+ onQueueUpdated(null, null,
+ MediaStatus.REPEAT_MODE_REPEAT_OFF,
+ false);
+ }
+ }
+ });
+
+ }
+ try {
+ LOGD(TAG, "Registering MediaChannel namespace");
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mRemoteMediaPlayer.getNamespace(),
+ mRemoteMediaPlayer);
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "attachMediaChannel()", e);
+ }
+ }
+
+ private void reattachMediaChannel() {
+ if (mRemoteMediaPlayer != null && mApiClient != null) {
+ try {
+ LOGD(TAG, "Registering MediaChannel namespace");
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
+ mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "reattachMediaChannel()", e);
+ }
+ }
+ }
+
+ private void detachMediaChannel() {
+ LOGD(TAG, "trying to detach media channel");
+ if (mRemoteMediaPlayer != null) {
+ try {
+ Cast.CastApi.removeMessageReceivedCallbacks(mApiClient,
+ mRemoteMediaPlayer.getNamespace());
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "detachMediaChannel()", e);
+ }
+ mRemoteMediaPlayer = null;
+ }
+ }
+
+ /**
+ * Returns the playback status of the remote device.
+ *
+ * @return Returns one of the values
+ *
+ * MediaStatus.PLAYER_STATE_UNKNOWN
+ * MediaStatus.PLAYER_STATE_IDLE
+ * MediaStatus.PLAYER_STATE_PLAYING
+ * MediaStatus.PLAYER_STATE_PAUSED
+ * MediaStatus.PLAYER_STATE_BUFFERING
+ *
+ */
+ public int getPlaybackStatus() {
+ return mState;
+ }
+
+ /**
+ * Returns the latest retrieved value for the {@link MediaStatus}. This value is updated
+ * whenever the onStatusUpdated callback is called.
+ */
+ public final MediaStatus getMediaStatus() {
+ return mMediaStatus;
+ }
+
+ /**
+ * Returns the Idle reason, defined in MediaStatus.IDLE_*
. Note that the returned
+ * value is only meaningful if the status is truly MediaStatus.PLAYER_STATE_IDLE
+ *
+ *
+ * Possible values are:
+ *
+ * IDLE_REASON_NONE
+ * IDLE_REASON_FINISHED
+ * IDLE_REASON_CANCELED
+ * IDLE_REASON_INTERRUPTED
+ * IDLE_REASON_ERROR
+ *
+ */
+ public int getIdleReason() {
+ return mIdleReason;
+ }
+
+ /*
+ * If a data namespace was provided when initializing this class, we set things up for a data
+ * channel
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ private void attachDataChannel() throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ if (TextUtils.isEmpty(mDataNamespace)) {
+ return;
+ }
+ if (mDataChannel != null) {
+ return;
+ }
+ checkConnectivity();
+ mDataChannel = new MessageReceivedCallback() {
+
+ @Override
+ public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onDataMessageReceived(message);
+ }
+ }
+ };
+ try {
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mDataNamespace, mDataChannel);
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "attachDataChannel()", e);
+ }
+ }
+
+ private void reattachDataChannel() {
+ if (!TextUtils.isEmpty(mDataNamespace) && mDataChannel != null) {
+ try {
+ Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mDataNamespace, mDataChannel);
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "reattachDataChannel()", e);
+ }
+ }
+ }
+
+ private void onMessageSendFailed(int errorCode) {
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onDataMessageSendFailed(errorCode);
+ }
+ }
+
+ /**
+ * Sends the message
on the data channel for the namespace that was provided
+ * during the initialization of this class. If messageId > 0
, then it has to be
+ * a unique identifier for the message; this id will be returned if an error occurs. If
+ * messageId == 0
, then an auto-generated unique identifier will be created and
+ * returned for the message.
+ *
+ * @throws IllegalStateException If the namespace is empty or null
+ * @throws NoConnectionException If no connectivity to the device exists
+ * @throws TransientNetworkDisconnectionException If framework is still trying to recover from
+ * a possibly transient loss of network
+ */
+ public void sendDataMessage(String message) throws TransientNetworkDisconnectionException,
+ NoConnectionException {
+ if (TextUtils.isEmpty(mDataNamespace)) {
+ throw new IllegalStateException("No Data Namespace is configured");
+ }
+ checkConnectivity();
+ Cast.CastApi.sendMessage(mApiClient, mDataNamespace, message)
+ .setResultCallback(new ResultCallback() {
+
+ @Override
+ public void onResult(Status result) {
+ if (!result.isSuccess()) {
+ VideoCastManager.this.onMessageSendFailed(result.getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Remove the custom data channel, if any. It returns true
if it succeeds
+ * otherwise if it encounters an error or if no connection exists or if no custom data channel
+ * exists, then it returns false
+ */
+ public boolean removeDataChannel() {
+ if (TextUtils.isEmpty(mDataNamespace)) {
+ return false;
+ }
+ try {
+ if (mApiClient != null) {
+ Cast.CastApi.removeMessageReceivedCallbacks(mApiClient, mDataNamespace);
+ }
+ mDataChannel = null;
+ mPreferenceAccessor.saveStringToPreference(PREFS_KEY_CAST_CUSTOM_DATA_NAMESPACE, null);
+ return true;
+ } catch (IOException | IllegalStateException e) {
+ LOGE(TAG, "removeDataChannel() failed to remove namespace " + mDataNamespace, e);
+ }
+ return false;
+
+ }
+
+ /*
+ * This is called by onStatusUpdated() of the RemoteMediaPlayer
+ */
+ private void onRemoteMediaPlayerStatusUpdated() {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated() reached");
+ if (mApiClient == null || mRemoteMediaPlayer == null
+ || mRemoteMediaPlayer.getMediaStatus() == null) {
+ LOGD(TAG, "mApiClient or mRemoteMediaPlayer is null, so will not proceed");
+ return;
+ }
+ mMediaStatus = mRemoteMediaPlayer.getMediaStatus();
+ List queueItems = mMediaStatus.getQueueItems();
+ if (queueItems != null) {
+ int itemId = mMediaStatus.getCurrentItemId();
+ MediaQueueItem item = mMediaStatus.getQueueItemById(itemId);
+ int repeatMode = mMediaStatus.getQueueRepeatMode();
+ boolean shuffle = false; //mMediaStatus.isShuffleEnabled();
+ onQueueUpdated(queueItems, item, repeatMode, shuffle);
+ } else {
+ onQueueUpdated(null, null, MediaStatus.REPEAT_MODE_REPEAT_OFF, false);
+ }
+ mState = mMediaStatus.getPlayerState();
+ mIdleReason = mMediaStatus.getIdleReason();
+
+ try {
+ double volume = getVolume();
+ boolean isMute = isMute();
+ boolean makeUiHidden = false;
+ if (mState == MediaStatus.PLAYER_STATE_PLAYING) {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = playing");
+ updateMediaSession(true);
+ long mediaDurationLeft = getMediaTimeRemaining();
+ startReconnectionService(mediaDurationLeft);
+ startNotificationService();
+ } else if (mState == MediaStatus.PLAYER_STATE_PAUSED) {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = paused");
+ updateMediaSession(false);
+ startNotificationService();
+ } else if (mState == MediaStatus.PLAYER_STATE_IDLE) {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = IDLE with reason: "
+ + mIdleReason );
+ updateMediaSession(false);
+ switch (mIdleReason) {
+ case MediaStatus.IDLE_REASON_FINISHED:
+ if (mMediaStatus.getLoadingItemId() == MediaQueueItem.INVALID_ITEM_ID) {
+ // we have reached the end of queue
+ clearMediaSession();
+ }
+ makeUiHidden = true;
+ break;
+ case MediaStatus.IDLE_REASON_ERROR:
+ // something bad happened on the cast device
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): IDLE reason = ERROR");
+ makeUiHidden = true;
+ clearMediaSession();
+ onFailed(R.string.ccl_failed_receiver_player_error, NO_STATUS_CODE);
+ break;
+ case MediaStatus.IDLE_REASON_CANCELED:
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): IDLE reason = CANCELLED");
+ makeUiHidden = !isRemoteStreamLive();
+ break;
+ case MediaStatus.IDLE_REASON_INTERRUPTED:
+ if (mMediaStatus.getLoadingItemId() == MediaQueueItem.INVALID_ITEM_ID) {
+ // we have reached the end of queue
+ clearMediaSession();
+ makeUiHidden = true;
+ }
+ break;
+ default:
+ LOGE(TAG, "onRemoteMediaPlayerStatusUpdated(): Unexpected Idle Reason "
+ + mIdleReason);
+ }
+ if (makeUiHidden) {
+ stopReconnectionService();
+ }
+ } else if (mState == MediaStatus.PLAYER_STATE_BUFFERING) {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = buffering");
+ } else {
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated(): Player status = unknown");
+ makeUiHidden = true;
+ }
+ if (makeUiHidden) {
+ stopNotificationService();
+ }
+ updateMiniControllersVisibility(!makeUiHidden);
+ updateMiniControllers();
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onRemoteMediaPlayerStatusUpdated();
+ consumer.onVolumeChanged(volume, isMute);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to get volume state due to network issues", e);
+ }
+
+ }
+
+ private void onRemoteMediaPreloadStatusUpdated() {
+ MediaQueueItem item = null;
+ mMediaStatus = mRemoteMediaPlayer.getMediaStatus();
+ if (mMediaStatus != null) {
+ item = mMediaStatus.getQueueItemById(mMediaStatus.getPreloadedItemId());
+ }
+ mPreLoadingItem = item;
+ updateMiniControllersVisibilityForUpcoming(item);
+ LOGD(TAG, "onRemoteMediaPreloadStatusUpdated() " + item);
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onRemoteMediaPreloadStatusUpdated(item);
+ }
+ }
+
+ public MediaQueueItem getPreLoadingItem() {
+ return mPreLoadingItem;
+ }
+
+ /*
+ * This is called by onQueueStatusUpdated() of RemoteMediaPlayer
+ */
+ private void onQueueUpdated(List queueItems, MediaQueueItem item,
+ int repeatMode, boolean shuffle) {
+ LOGD(TAG, "onQueueUpdated() reached");
+ LOGD(TAG, String.format("Queue Items size: %d, Item: %s, Repeat Mode: %d, Shuffle: %s",
+ queueItems == null ? 0 : queueItems.size(), item, repeatMode, shuffle));
+ if (queueItems != null) {
+ mMediaQueue = new MediaQueue(new CopyOnWriteArrayList<>(queueItems), item, shuffle,
+ repeatMode);
+ } else {
+ mMediaQueue = new MediaQueue(new CopyOnWriteArrayList(), null, false,
+ MediaStatus.REPEAT_MODE_REPEAT_OFF);
+ }
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onMediaQueueUpdated(queueItems, item, repeatMode, shuffle);
+ }
+ }
+
+ /*
+ * This is called by onMetadataUpdated() of RemoteMediaPlayer
+ */
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ LOGD(TAG, "onRemoteMediaPlayerMetadataUpdated() reached");
+ updateMediaSessionMetadata();
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onRemoteMediaPlayerMetadataUpdated();
+ }
+ try {
+ updateLockScreenImage(getRemoteMediaInformation());
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update lock screen metadata due to a network issue", e);
+ }
+ }
+
+ /**
+ * Returns the Media Session Token. If there is no media session, it returns {@code null}
+ */
+ public MediaSessionCompat.Token getMediaSessionCompatToken() {
+ return mMediaSessionCompat == null ? null : mMediaSessionCompat.getSessionToken();
+ }
+
+ /*
+ * Sets up the {@link MediaSessionCompat} for this application. It also handles the audio
+ * focus.
+ */
+ @SuppressLint("InlinedApi")
+ private void setUpMediaSession(final MediaInfo info) {
+ if (!isFeatureEnabled(BaseCastManager.FEATURE_LOCKSCREEN)) {
+ return;
+ }
+ if (mMediaSessionCompat == null) {
+ mMediaEventReceiver = new ComponentName(mContext, VideoIntentReceiver.class.getName());
+ mMediaSessionCompat = new MediaSessionCompat(mContext, "TAG", mMediaEventReceiver,
+ null);
+ mMediaSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
+ | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mMediaSessionCompat.setActive(true);
+ mMediaSessionCompat.setCallback(new MediaSessionCompat.Callback() {
+ @Override
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ KeyEvent keyEvent = mediaButtonIntent
+ .getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (keyEvent != null && (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE
+ || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY)) {
+ toggle();
+ }
+ return true;
+ }
+
+ @Override
+ public void onPlay() {
+ toggle();
+ }
+
+ @Override
+ public void onPause() {
+ toggle();
+ }
+
+ private void toggle() {
+ try {
+ togglePlayback();
+ } catch (CastException | TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ LOGE(TAG, "MediaSessionCompat.Callback(): Failed to toggle playback", e);
+ }
+ }
+ });
+ }
+
+ mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
+
+ mMediaSessionCompat.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
+ .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
+
+ // Update the media session's image
+ updateLockScreenImage(info);
+
+ // update the media session's metadata
+ updateMediaSessionMetadata();
+
+ mMediaRouter.setMediaSessionCompat(mMediaSessionCompat);
+ }
+
+ /*
+ * Updates lock screen image
+ */
+ private void updateLockScreenImage(final MediaInfo info) {
+ if (info == null) {
+ return;
+ }
+ setBitmapForLockScreen(info);
+ }
+
+ /*
+ * Sets the appropriate {@link Bitmap} for the right size image for lock screen. In ICS and
+ * JB, the image shown on the lock screen is a small size bitmap but for KitKat, the image is a
+ * full-screen image so we need to separately handle these two cases.
+ */
+ private void setBitmapForLockScreen(MediaInfo video) {
+ if (video == null || mMediaSessionCompat == null) {
+ return;
+ }
+ Uri imgUrl = null;
+ Bitmap bm = null;
+ List images = video.getMetadata().getImages();
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (images.size() > 1) {
+ imgUrl = images.get(1).getUrl();
+ } else if (images.size() == 1) {
+ imgUrl = images.get(0).getUrl();
+ } else if (mContext != null) {
+ // we don't have a url for image so get a placeholder image from resources
+ bm = BitmapFactory.decodeResource(mContext.getResources(),
+ R.drawable.album_art_placeholder_large);
+ }
+ } else if (!images.isEmpty()) {
+ imgUrl = images.get(0).getUrl();
+ } else {
+ // we don't have a url for image so get a placeholder image from resources
+ bm = BitmapFactory.decodeResource(mContext.getResources(),
+ R.drawable.album_art_placeholder);
+ }
+ if (bm != null) {
+ MediaMetadataCompat currentMetadata = mMediaSessionCompat.getController().getMetadata();
+ MediaMetadataCompat.Builder newBuilder = currentMetadata == null
+ ? new MediaMetadataCompat.Builder()
+ : new MediaMetadataCompat.Builder(currentMetadata);
+ mMediaSessionCompat.setMetadata(newBuilder
+ .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bm)
+ .build());
+ } else {
+ if (mLockScreenFetchTask != null) {
+ mLockScreenFetchTask.cancel(true);
+ }
+ mLockScreenFetchTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (mMediaSessionCompat != null) {
+ MediaMetadataCompat currentMetadata = mMediaSessionCompat.getController()
+ .getMetadata();
+ MediaMetadataCompat.Builder newBuilder = currentMetadata == null
+ ? new MediaMetadataCompat.Builder()
+ : new MediaMetadataCompat.Builder(currentMetadata);
+ mMediaSessionCompat.setMetadata(newBuilder
+ .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap)
+ .build());
+ }
+ mLockScreenFetchTask = null;
+ }
+ };
+ mLockScreenFetchTask.execute(imgUrl);
+ }
+ }
+ /*
+ * Updates the playback status of the Media Session
+ */
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void updateMediaSession(boolean playing) {
+ if (!isFeatureEnabled(FEATURE_LOCKSCREEN)) {
+ return;
+ }
+ if (!isConnected()) {
+ return;
+ }
+ try {
+ if ((mMediaSessionCompat == null) && playing) {
+ setUpMediaSession(getRemoteMediaInformation());
+ }
+ if (mMediaSessionCompat != null) {
+ int playState = isRemoteStreamLive() ? PlaybackStateCompat.STATE_BUFFERING
+ : PlaybackStateCompat.STATE_PLAYING;
+ int state = playing ? playState : PlaybackStateCompat.STATE_PAUSED;
+
+ mMediaSessionCompat.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(state, 0, 1.0f)
+ .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to set up MediaSessionCompat due to network issues", e);
+ }
+ }
+
+ /*
+ * On ICS and JB, lock screen metadata is one liner: Title - Album Artist - Album. On KitKat, it
+ * has two lines: Title , Album Artist - Album
+ */
+ private void updateMediaSessionMetadata() {
+ if ((mMediaSessionCompat == null) || !isFeatureEnabled(FEATURE_LOCKSCREEN)) {
+ return;
+ }
+
+ try {
+ MediaInfo info = getRemoteMediaInformation();
+ if (info == null) {
+ return;
+ }
+ final MediaMetadata mm = info.getMetadata();
+ MediaMetadataCompat currentMetadata = mMediaSessionCompat.getController().getMetadata();
+ MediaMetadataCompat.Builder newBuilder = currentMetadata == null
+ ? new MediaMetadataCompat.Builder()
+ : new MediaMetadataCompat.Builder(currentMetadata);
+ MediaMetadataCompat metadata = newBuilder
+ // used in lock screen for pre-lollipop
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE,
+ mm.getString(MediaMetadata.KEY_TITLE))
+ // used in lock screen for pre-lollipop
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST,
+ mContext.getResources().getString(
+ R.string.ccl_casting_to_device, getDeviceName()))
+ // used in MediaRouteController dialog
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
+ mm.getString(MediaMetadata.KEY_TITLE))
+ // used in MediaRouteController dialog
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
+ mm.getString(MediaMetadata.KEY_SUBTITLE))
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,
+ info.getStreamDuration())
+ .build();
+ mMediaSessionCompat.setMetadata(metadata);
+
+ Uri iconUri = mm.hasImages() ? mm.getImages().get(0).getUrl() : null;
+ if (iconUri == null) {
+ Bitmap bm = BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.album_art_placeholder);
+ mMediaSessionCompat.setMetadata(newBuilder
+ .putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bm)
+ .build());
+ } else {
+ if (mMediaSessionIconFetchTask != null) {
+ mMediaSessionIconFetchTask.cancel(true);
+ }
+ mMediaSessionIconFetchTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (mMediaSessionCompat != null) {
+ MediaMetadataCompat currentMetadata = mMediaSessionCompat
+ .getController().getMetadata();
+ MediaMetadataCompat.Builder newBuilder = currentMetadata == null
+ ? new MediaMetadataCompat.Builder()
+ : new MediaMetadataCompat.Builder(currentMetadata);
+ mMediaSessionCompat.setMetadata(newBuilder.putBitmap(
+ MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap).build());
+ }
+ mMediaSessionIconFetchTask = null;
+ }
+ };
+ mMediaSessionIconFetchTask.execute(iconUri);
+ }
+
+ } catch (NotFoundException e) {
+ LOGE(TAG, "Failed to update Media Session due to resource not found", e);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update Media Session due to network issues", e);
+ }
+ }
+
+ /*
+ * Clears Media Session
+ */
+ public void clearMediaSession() {
+ LOGD(TAG, "clearMediaSession()");
+ if (isFeatureEnabled(FEATURE_LOCKSCREEN)) {
+ if (mLockScreenFetchTask != null) {
+ mLockScreenFetchTask.cancel(true);
+ }
+ if (mMediaSessionIconFetchTask != null) {
+ mMediaSessionIconFetchTask.cancel(true);
+ }
+ mAudioManager.abandonAudioFocus(null);
+ if (mMediaSessionCompat != null) {
+ mMediaSessionCompat.setMetadata(null);
+ PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f).build();
+ mMediaSessionCompat.setPlaybackState(playbackState);
+ mMediaSessionCompat.release();
+ mMediaSessionCompat.setActive(false);
+ mMediaSessionCompat = null;
+ }
+ }
+ }
+
+ /**
+ * Registers an
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer}
+ * interface with this class. Registered listeners will be notified of changes to a variety of
+ * lifecycle and media status changes through the callbacks that the interface provides.
+ *
+ * @see VideoCastConsumerImpl
+ */
+ public synchronized void addVideoCastConsumer(VideoCastConsumer listener) {
+ if (listener != null) {
+ addBaseCastConsumer(listener);
+ mVideoConsumers.add(listener);
+ LOGD(TAG, "Successfully added the new CastConsumer listener " + listener);
+ }
+ }
+
+ /**
+ * Unregisters an
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer}.
+ */
+ public synchronized void removeVideoCastConsumer(VideoCastConsumer listener) {
+ if (listener != null) {
+ removeBaseCastConsumer(listener);
+ mVideoConsumers.remove(listener);
+ }
+ }
+
+ /**
+ * Adds a new {@link IMiniController} component. Callers need to provide their own
+ * {@link OnMiniControllerChangedListener}.
+ *
+ * @see {@link #removeMiniController(IMiniController)}
+ */
+ public void addMiniController(IMiniController miniController,
+ OnMiniControllerChangedListener onChangedListener) {
+ if (miniController != null) {
+ boolean result;
+ synchronized (mMiniControllers) {
+ result = mMiniControllers.add(miniController);
+ }
+ if (result) {
+ miniController.setOnMiniControllerChangedListener(onChangedListener == null ? this
+ : onChangedListener);
+ try {
+ if (isConnected() && isRemoteMediaLoaded()) {
+ updateMiniController(miniController);
+ miniController.setVisibility(View.VISIBLE);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to get the status of media playback on receiver", e);
+ }
+ LOGD(TAG, "Successfully added the new MiniController " + miniController);
+ } else {
+ LOGD(TAG, "Attempting to adding " + miniController + " but it was already "
+ + "registered, skipping this step");
+ }
+ }
+ }
+
+ /**
+ * Adds a new {@link IMiniController} component and assigns {@link VideoCastManager} as the
+ * {@link OnMiniControllerChangedListener} for this component.
+ */
+ public void addMiniController(IMiniController miniController) {
+ addMiniController(miniController, null);
+ }
+
+ /**
+ * Removes a {@link IMiniController} listener from the list of listeners.
+ */
+ public void removeMiniController(IMiniController listener) {
+ if (listener != null) {
+ listener.setOnMiniControllerChangedListener(null);
+ synchronized (mMiniControllers) {
+ mMiniControllers.remove(listener);
+ }
+ }
+ }
+
+ @Override
+ protected void onDeviceUnselected() {
+ stopNotificationService();
+ detachMediaChannel();
+ removeDataChannel();
+ mState = MediaStatus.PLAYER_STATE_IDLE;
+ }
+
+ @Override
+ protected Builder getCastOptionBuilder(CastDevice device) {
+ Builder builder = Cast.CastOptions.builder(mSelectedCastDevice, new CastListener());
+ if (isFeatureEnabled(FEATURE_DEBUGGING)) {
+ builder.setVerboseLoggingEnabled(true);
+ }
+ return builder;
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ super.onConnectionFailed(result);
+ updateMediaSession(false);
+ stopNotificationService();
+ }
+
+ @Override
+ public void onDisconnected(boolean stopAppOnExit, boolean clearPersistedConnectionData,
+ boolean setDefaultRoute) {
+ super.onDisconnected(stopAppOnExit, clearPersistedConnectionData, setDefaultRoute);
+ updateMiniControllersVisibility(false);
+ if (clearPersistedConnectionData && !mConnectionSuspended) {
+ clearMediaSession();
+ }
+ mState = MediaStatus.PLAYER_STATE_IDLE;
+ mMediaQueue = null;
+ }
+
+ @Override
+ protected MediaRouteDialogFactory getMediaRouteDialogFactory() {
+ return new VideoMediaRouteDialogFactory();
+ }
+
+ class CastListener extends Cast.Listener {
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.gms.cast.Cast.Listener#onApplicationDisconnected (int)
+ */
+ @Override
+ public void onApplicationDisconnected(int statusCode) {
+ VideoCastManager.this.onApplicationDisconnected(statusCode);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.android.gms.cast.Cast.Listener#onApplicationStatusChanged ()
+ */
+ @Override
+ public void onApplicationStatusChanged() {
+ VideoCastManager.this.onApplicationStatusChanged();
+ }
+
+ @Override
+ public void onVolumeChanged() {
+ VideoCastManager.this.onVolumeChanged();
+ }
+ }
+
+ @Override
+ public void onFailed(int resourceId, int statusCode) {
+ LOGD(TAG, "onFailed: " + mContext.getString(resourceId) + ", code: " + statusCode);
+ super.onFailed(resourceId, statusCode);
+ }
+
+ /**
+ * Returns the class for the full screen activity that can control the remote media playback.
+ * This activity will also be invoked from the notification shade. If {@code null} is returned,
+ * this library will use a default implementation.
+ *
+ * @see {@link VideoCastControllerActivity}
+ */
+ public Class> getTargetActivity() {
+ return mTargetActivity;
+ }
+
+ /**
+ * Clients can call this method to delegate handling of the volume. Clients should override
+ * {@code dispatchEvent} and call this method:
+ *
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mCastManager.onDispatchVolumeKeyEvent(event, VOLUME_DELTA)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+ *
+ * @param event The dispatched event.
+ * @param volumeDelta The amount by which volume should be increased or decreased in each step
+ * @return true
if volume is handled by the library, false
otherwise.
+ */
+ public boolean onDispatchVolumeKeyEvent(KeyEvent event, double volumeDelta) {
+ if (isConnected()) {
+ boolean isKeyDown = event.getAction() == KeyEvent.ACTION_DOWN;
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ if (changeVolume(volumeDelta, isKeyDown)) {
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ if (changeVolume(-volumeDelta, isKeyDown)) {
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ private boolean changeVolume(double volumeIncrement, boolean isKeyDown) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ && getPlaybackStatus() == MediaStatus.PLAYER_STATE_PLAYING
+ && isFeatureEnabled(BaseCastManager.FEATURE_LOCKSCREEN)) {
+ return false;
+ }
+
+ if (isKeyDown) {
+ try {
+ adjustVolume(volumeIncrement);
+ } catch (CastException | TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ LOGE(TAG, "Failed to change volume", e);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets the volume step, i.e. the fraction by which volume will increase or decrease each time
+ * user presses the hard volume buttons on the device.
+ *
+ * @param volumeStep Should be a double between 0 and 1, inclusive.
+ */
+ public VideoCastManager setVolumeStep(double volumeStep) {
+ if ((volumeStep > 1) || (volumeStep < 0)) {
+ throw new IllegalArgumentException("Volume Step should be between 0 and 1, inclusive");
+ }
+ mVolumeStep = volumeStep;
+ return this;
+ }
+
+ /**
+ * Returns the volume step. The default value is {@code DEFAULT_VOLUME_STEP}.
+ */
+ public double getVolumeStep() {
+ return mVolumeStep;
+ }
+
+ /**
+ * Set the live stream duration; this is purely used in the reconnection logic. If this method
+ * is not called, the default value {@code DEFAULT_LIVE_STREAM_DURATION_MS} is used.
+ *
+ * @param duration Duration, specified in milliseconds.
+ */
+ public void setLiveStreamDuration(long duration) {
+ mLiveStreamDuration = duration;
+ }
+
+ /**
+ * Sets the active tracks for the currently loaded media.
+ */
+ public void setActiveTrackIds(long[] trackIds) {
+ if (mRemoteMediaPlayer == null || mRemoteMediaPlayer.getMediaInfo() == null) {
+ return;
+ }
+ mRemoteMediaPlayer.setActiveMediaTracks(mApiClient, trackIds)
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(MediaChannelResult mediaChannelResult) {
+ LOGD(TAG, "Setting track result was successful? "
+ + mediaChannelResult.getStatus().isSuccess());
+ if (!mediaChannelResult.getStatus().isSuccess()) {
+ LOGD(TAG, "Failed since: " + mediaChannelResult.getStatus()
+ + " and status code:" + mediaChannelResult.getStatus()
+ .getStatusCode());
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets or updates the style of the Text Track.
+ */
+ public void setTextTrackStyle(TextTrackStyle style) {
+ mRemoteMediaPlayer.setTextTrackStyle(mApiClient, style)
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_to_set_track_style,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ try {
+ consumer.onTextTrackStyleChanged(style);
+ } catch (Exception e) {
+ LOGE(TAG, "onTextTrackStyleChanged(): Failed to inform " + consumer, e);
+ }
+ }
+ }
+
+ /**
+ * Signals a change in the Text Track style. Clients should not call this directly.
+ */
+ public void onTextTrackStyleChanged(TextTrackStyle style) {
+ LOGD(TAG, "onTextTrackStyleChanged() reached");
+ if (mRemoteMediaPlayer == null || mRemoteMediaPlayer.getMediaInfo() == null) {
+ return;
+ }
+ mRemoteMediaPlayer.setTextTrackStyle(mApiClient, style)
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(MediaChannelResult result) {
+ if (!result.getStatus().isSuccess()) {
+ onFailed(R.string.ccl_failed_to_set_track_style,
+ result.getStatus().getStatusCode());
+ }
+ }
+ });
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ try {
+ consumer.onTextTrackStyleChanged(style);
+ } catch (Exception e) {
+ LOGE(TAG, "onTextTrackStyleChanged(): Failed to inform " + consumer, e);
+ }
+ }
+ }
+
+ /**
+ * Signals a change in the Text Track on/off state. Clients should not call this directly.
+ */
+ public void onTextTrackEnabledChanged(boolean isEnabled) {
+ LOGD(TAG, "onTextTrackEnabledChanged() reached");
+ if (!isEnabled) {
+ setActiveTrackIds(new long[]{});
+ }
+
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onTextTrackEnabledChanged(isEnabled);
+ }
+ }
+
+ /**
+ * Signals a change in the Text Track locale. Clients should not call this directly.
+ */
+ public void onTextTrackLocaleChanged(Locale locale) {
+ LOGD(TAG, "onTextTrackLocaleChanged() reached");
+ for (VideoCastConsumer consumer : mVideoConsumers) {
+ consumer.onTextTrackLocaleChanged(locale);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private void registerCaptionListener(final Context context) {
+ if (Utils.IS_KITKAT_OR_ABOVE) {
+ CaptioningManager captioningManager =
+ (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+ captioningManager.addCaptioningChangeListener(
+ new CaptioningManager.CaptioningChangeListener() {
+ @Override
+ public void onEnabledChanged(boolean enabled) {
+ onTextTrackEnabledChanged(enabled);
+ }
+
+ @Override
+ public void onUserStyleChanged(
+ CaptioningManager.CaptionStyle userStyle) {
+ onTextTrackStyleChanged(mTrackManager.getTextTrackStyle());
+ }
+
+ @Override
+ public void onFontScaleChanged(float fontScale) {
+ onTextTrackStyleChanged(mTrackManager.getTextTrackStyle());
+ }
+
+ @Override
+ public void onLocaleChanged(Locale locale) {
+ onTextTrackLocaleChanged(locale);
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Updates the summary of the captions between "on" and "off" based on the user selected
+ * preferences. This can be called by the caller application when they add captions settings to
+ * their preferences. Preferably this should be called in the {@code onResume()} of the
+ * PreferenceActivity so that it gets updated when needed.
+ */
+ public void updateCaptionSummary(String captionScreenKey, PreferenceScreen preferenceScreen) {
+ int status = R.string.ccl_info_na;
+ if (isFeatureEnabled(FEATURE_CAPTIONS_PREFERENCE)) {
+ status = mTrackManager.isCaptionEnabled() ? R.string.ccl_on : R.string.ccl_off;
+ }
+ preferenceScreen.findPreference(captionScreenKey)
+ .setSummary(status);
+ }
+
+ /**
+ * Returns the instance of {@link TracksPreferenceManager} that is being used.
+ */
+ public TracksPreferenceManager getTracksPreferenceManager() {
+ return mTrackManager;
+ }
+
+ /**
+ * Returns the list of current active tracks. If there is no remote media, then this will
+ * return null
.
+ */
+ public long[] getActiveTrackIds() {
+ if (mRemoteMediaPlayer != null && mRemoteMediaPlayer.getMediaStatus() != null) {
+ return mRemoteMediaPlayer.getMediaStatus().getActiveTrackIds();
+ }
+ return null;
+ }
+
+ /**
+ * Adds an
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.tracks.OnTracksSelectedListener} // NOLINT
+ * to the lis of listeners.
+ */
+ public void addTracksSelectedListener(OnTracksSelectedListener listener) {
+ if (listener != null) {
+ mTracksSelectedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes an
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.tracks.OnTracksSelectedListener} // NOLINT
+ * from the lis of listeners.
+ */
+ public void removeTracksSelectedListener(OnTracksSelectedListener listener) {
+ if (listener != null) {
+ mTracksSelectedListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Notifies all the
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.tracks.OnTracksSelectedListener} // NOLINT
+ * that the set of active tracks has changed.
+ *
+ * @param tracks the set of active tracks. Must be {@code non-null} but can be an empty list.
+ */
+ public void notifyTracksSelectedListeners(List tracks) {
+ if (tracks == null) {
+ throw new IllegalArgumentException("tracks must not be null");
+ }
+ for (OnTracksSelectedListener listener : mTracksSelectedListeners) {
+ listener.onTracksSelected(tracks);
+ }
+ }
+
+ public final MediaQueue getMediaQueue() {
+ return mMediaQueue;
+ }
+
+ private void stopProgressTimer() {
+ LOGD(TAG, "Stopped TrickPlay Timer");
+ if (mProgressTask != null) {
+ mProgressTask.cancel();
+ mProgressTask = null;
+ }
+ if (mProgressTimer != null) {
+ mProgressTimer.cancel();
+ mProgressTimer = null;
+ }
+ }
+
+ private void restartProgressTimer() {
+ stopProgressTimer();
+ mProgressTimer = new Timer();
+ mProgressTask = new UpdateProgressTask();
+ mProgressTimer.scheduleAtFixedRate(mProgressTask, 100, PROGRESS_UPDATE_INTERVAL_MS);
+ LOGD(TAG, "Restarted Progress Timer");
+ }
+
+ private class UpdateProgressTask extends TimerTask {
+
+ @Override
+ public void run() {
+ int currentPos;
+ if (mState == MediaStatus.PLAYER_STATE_BUFFERING || !isConnected()
+ || mRemoteMediaPlayer == null) {
+ return;
+ }
+ try {
+ int duration = (int) getMediaDuration();
+ if (duration > 0) {
+ currentPos = (int) getCurrentMediaPosition();
+ updateProgress(currentPos, duration);
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update the progress tracker due to network issues", e);
+ }
+ }
+ }
+
+ /**
+ * Note: This is called on a worker thread
+ */
+ private void updateProgress(int currentPosition, int duration) {
+ synchronized (mMiniControllers) {
+ for (final IMiniController controller : mMiniControllers) {
+ controller.setProgress(currentPosition, duration);
+ }
+ }
+ }
+
+ /**
+ * Sets the policy to be used for the visibility of skip forward/backward on the {@link
+ * VideoCastControllerActivity}. Note that the new policy is enforced the next time that
+ * activity is opened and does not apply to the currently runnig one, if any.
+ *
+ * @param policy can be one of {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_DISABLED},
+ * {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_HIDDEN} or
+ * {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_ALWAYS}.
+ */
+ public void setNextPreviousVisibilityPolicy(final int policy) {
+ switch(policy) {
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_DISABLED:
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_ALWAYS:
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_HIDDEN:
+ mPreferenceAccessor.saveIntToPreference(PREFS_KEY_NEXT_PREV_POLICY, policy);
+ return;
+ default:
+ LOGD(TAG, "Invalid value for the NextPreviousVisibilityPolicy was requested");
+ }
+ throw new IllegalArgumentException(
+ "Invalid value for the NextPreviousVisibilityPolicy was requested");
+ }
+
+ /**
+ * Turns on/off the immersive mode for the full screen cast controller
+ * {@link VideoCastControllerActivity}. Calls to this will take effect the next time that
+ * activity is launched so it is recommended to be called early in the application lifecycle.
+ */
+ public void setCastControllerImmersive(boolean mode) {
+ mPreferenceAccessor.saveBooleanToPreference(PREFS_KEY_IMMERSIVE_MODE, mode);
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumer.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumer.java
new file mode 100644
index 000000000..be67c0347
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.OnFailedListener;
+
+import android.support.v7.media.MediaRouter.RouteInfo;
+
+/**
+ * An interface for receiving callbacks around the connectivity status to a Cast device.
+ */
+public interface BaseCastConsumer extends OnFailedListener {
+
+ /**
+ * Called when connection is established
+ */
+ void onConnected();
+
+ /**
+ * Called when the client is temporarily in a disconnected state. This can happen if there is a
+ * problem with the remote service (e.g. a crash or resource problem causes it to be killed by
+ * the system). When called, all requests have been canceled and no outstanding listeners will
+ * be executed. Applications could disable UI components that require the service, and wait for
+ * a call to onConnectivityRecovered() to re-enable them.
+ *
+ * @param cause The reason for the disconnection. Defined by constants CAUSE_*.
+ */
+ void onConnectionSuspended(int cause);
+
+ /**
+ * Called when a device is disconnected
+ */
+ void onDisconnected();
+
+ /**
+ * Called when a device is disconnected or fails to reconnect and provides a reason for the
+ * disconnect or failure.
+ *
+ * @param reason The failure/disconnect reason; can be one of the following:
+ *
+ * {@link BaseCastManager#DISCONNECT_REASON_APP_NOT_RUNNING}
+ * {@link BaseCastManager#DISCONNECT_REASON_EXPLICIT}
+ * {@link BaseCastManager#DISCONNECT_REASON_CONNECTIVITY}
+ * {@link BaseCastManager#DISCONNECT_REASON_OTHER}
+ * @BaseCastManager.DISCONNECT_REASON
+ */
+ void onDisconnectionReason(@BaseCastManager.DISCONNECT_REASON int reason);
+
+ /**
+ * Called when an error happens while connecting to a device.
+ */
+ void onConnectionFailed(ConnectionResult result);
+
+ /**
+ * Called when the MediaRouterCallback detects a non-default route.
+ */
+ void onCastDeviceDetected(RouteInfo info);
+
+ /**
+ * Called when the number of cast devices present on the network changes from 0 to a positive
+ * number or vice versa. Can be used, for example, to control the visibility of {@link
+ * android.support.v7.app.MediaRouteButton}
+ *
+ * @param castPresent set to {@code true} if at least one device becomes available,
+ * {@code false} otherwise
+ */
+ void onCastAvailabilityChanged(boolean castPresent);
+
+ /**
+ * Called after reconnection is established following a temporary disconnection, say, due to
+ * network issues.
+ */
+ void onConnectivityRecovered();
+
+ /**
+ * Called when visibility of the application has changed.
+ */
+ void onUiVisibilityChanged(boolean visible);
+
+ /**
+ * Called when the status of reconnection changes.
+ * @param status
+ */
+ void onReconnectionStatusChanged(int status);
+
+ /**
+ * Called when a device is selected/unselected.
+ * @param device
+ */
+ void onDeviceSelected(CastDevice device);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumerImpl.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumerImpl.java
new file mode 100644
index 000000000..09cef6c6d
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/BaseCastConsumerImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+
+import android.support.v7.media.MediaRouter.RouteInfo;
+
+/**
+ * A no-op implementation of the {@link BaseCastConsumer}
+ */
+public class BaseCastConsumerImpl implements BaseCastConsumer {
+
+ @Override
+ public void onConnected() {
+ // no-op
+ }
+
+ @Override
+ public void onDisconnected() {
+ // no-op
+ }
+
+ @Override
+ public void onDisconnectionReason(@BaseCastManager.DISCONNECT_REASON int reason) {
+ // no-op
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ // no-op
+ }
+
+ @Override
+ public void onCastDeviceDetected(RouteInfo info) {
+ // no-op
+ }
+
+ @Override
+ public void onCastAvailabilityChanged(boolean castPresent) {
+ // no-op
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ // no-op
+ }
+
+ @Override
+ public void onConnectivityRecovered() {
+ // no-op
+ }
+
+ @Override
+ public void onUiVisibilityChanged(boolean visible) {
+ // no-op
+ }
+
+ @Override
+ public void onReconnectionStatusChanged(int status) {
+ // no-op
+ }
+
+ @Override
+ public void onDeviceSelected(CastDevice device) {
+ // no-op
+ }
+
+ @Override
+ public void onFailed(int resourceId, int statusCode) {
+ // no-op
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumer.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumer.java
new file mode 100644
index 000000000..367e9e687
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.common.api.Status;
+
+/**
+ * An interface that extends {@link BaseCastConsumer} and adds callbacks for application lifecycle
+ * and success or failure of message exchange with a cast device.
+ */
+public interface DataCastConsumer extends BaseCastConsumer {
+ /**
+ * Called when the application is successfully launched or joined. Upon successful connection, a
+ * session ID is returned. wasLaunched
indicates if the application was launched or
+ * joined.
+ */
+ void onApplicationConnected(ApplicationMetadata appMetadata,
+ String applicationStatus, String sessionId, boolean wasLaunched);
+
+ /**
+ * Called when the current application has stopped
+ */
+ void onApplicationDisconnected(int errorCode);
+
+ /**
+ * Called when an attempt to stop a receiver application has failed.
+ */
+ void onApplicationStopFailed(int errorCode);
+
+ /**
+ * Called when an application launch has failed. Failure reason is captured in the
+ * errorCode
argument. Here is a list of possible values:
+ *
+ * 4 : Application not found
+ * 5 : Application not currently running
+ * 6 : Application already running
+ *
+ */
+ void onApplicationConnectionFailed(int errorCode);
+
+ /**
+ * Called when application status changes. The argument is built by the receiver
+ */
+ void onApplicationStatusChanged(String appStatus);
+
+ /**
+ * Called when the device's volume is changed. Note not to mix that with the stream's volume
+ */
+ void onVolumeChanged(double value, boolean isMute);
+
+ /**
+ * Called when a message is received from a given {@link CastDevice} for a given
+ * namespace
.
+ */
+ void onMessageReceived(CastDevice castDevice, String namespace, String message);
+
+ /**
+ * Called when there is an error sending a message.
+ *
+ * @param status The status of the result
+ */
+ void onMessageSendFailed(Status status);
+
+ /**
+ * Called when this callback is removed from the Cast object.
+ *
+ * @param castDevice The castDevice from where the message originated.
+ * @param namespace The associated namespace of the removed listener.
+ */
+ void onRemoved(CastDevice castDevice, String namespace);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumerImpl.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumerImpl.java
new file mode 100644
index 000000000..54ecba6c6
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/DataCastConsumerImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.common.api.Status;
+
+/**
+ * A no-op implementation of the {@link DataCastConsumer}
+ */
+public class DataCastConsumerImpl extends BaseCastConsumerImpl implements DataCastConsumer {
+
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String applicationStatus,
+ String sessionId, boolean wasLaunched) {
+ }
+
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ }
+
+ @Override
+ public void onApplicationStopFailed(int errorCode) {
+ }
+
+ @Override
+ public void onApplicationConnectionFailed(int errorCode) {
+ }
+
+ @Override
+ public void onApplicationStatusChanged(String appStatus) {
+ }
+
+ @Override
+ public void onVolumeChanged(double value, boolean isMute) {
+ }
+
+ @Override
+ public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
+ }
+
+ @Override
+ public void onMessageSendFailed(Status status) {
+ }
+
+ @Override
+ public void onRemoved(CastDevice castDevice, String namespace) {
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumer.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumer.java
new file mode 100644
index 000000000..bbcb2f172
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumer.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.cast.Cast;
+import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.TextTrackStyle;
+
+import android.view.View;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * An interface that extends {@link BaseCastConsumer} and
+ * adds callbacks related to the lifecycle of a video-centric application.
+ */
+public interface VideoCastConsumer extends BaseCastConsumer {
+
+ /**
+ * Called when the application is successfully launched or joined. Upon successful connection, a
+ * session ID is returned. wasLaunched
indicates if the application was launched or
+ * joined.
+ */
+ void onApplicationConnected(ApplicationMetadata appMetadata,
+ String sessionId, boolean wasLaunched);
+
+ /**
+ * Called when an application launch has failed. Failure reason is captured in the
+ * errorCode
argument. Here is a list of possible values:
+ *
+ * {@link com.google.android.gms.cast.CastStatusCodes#APPLICATION_NOT_FOUND}
+ * {@link com.google.android.gms.cast.CastStatusCodes#APPLICATION_NOT_RUNNING}
+ *
+ */
+ void onApplicationConnectionFailed(int errorCode);
+
+ /**
+ * Called when an attempt to stop a receiver application has failed.
+ */
+ void onApplicationStopFailed(int errorCode);
+
+ /**
+ * Called when application status changes. The argument is built by the receiver
+ */
+ void onApplicationStatusChanged(String appStatus);
+
+ /**
+ * Called when the device's volume is changed. Note not to mix that with the stream's volume
+ */
+ void onVolumeChanged(double value, boolean isMute);
+
+ /**
+ * Called when the current application has stopped
+ */
+ void onApplicationDisconnected(int errorCode);
+
+ /**
+ * Called when metadata of the current media changes
+ */
+ void onRemoteMediaPlayerMetadataUpdated();
+
+ /**
+ * Called when media's status updated.
+ */
+ void onRemoteMediaPlayerStatusUpdated();
+
+ /**
+ * Called when the data channel callback is removed from the {@link Cast} object.
+ */
+ void onNamespaceRemoved();
+
+ /**
+ * Called when there is an error sending a message.
+ *
+ * @param errorCode An error code indicating the reason for the disconnect. One of the error
+ * constants defined in CastErrors.
+ */
+ void onDataMessageSendFailed(int errorCode);
+
+ /**
+ * Called when a message is received from a given {@link CastDevice}.
+ *
+ * @param message The received payload for the message.
+ */
+ void onDataMessageReceived(String message);
+
+ /**
+ * Called when the style of the text caption has changed
+ * @param style The new style
+ */
+ void onTextTrackStyleChanged(TextTrackStyle style);
+
+ /**
+ * Called when Close Captions on/off is changed
+ */
+ void onTextTrackEnabledChanged(boolean isEnabled);
+
+ /**
+ * Called when the locale for the caption has changed
+ */
+ void onTextTrackLocaleChanged(Locale locale);
+
+ /**
+ * A callback to inform the client of the result of a
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager#loadMedia}
+ * request
+ *
+ * @param statusCode The status code that represents the success or failure of the request.
+ * The possible value are defined in
+ * {@link com.google.android.gms.common.api.CommonStatusCodes} or
+ * {@link com.google.android.gms.cast.CastStatusCodes}.
+ * {@link com.google.android.gms.cast.CastStatusCodes#SUCCESS} signifies a successful request.
+ */
+ void onMediaLoadResult(int statusCode);
+
+ /**
+ * A callback to inform the clients that queue has been updated.
+ *
+ * @param queueItems The updated list of queue items
+ * @param item The item that was updated
+ * @param repeatMode The repeat mode of the updated item
+ * @param shuffle The shuffle status of the updated item
+ */
+ void onMediaQueueUpdated(List queueItems, MediaQueueItem item,
+ int repeatMode, boolean shuffle);
+
+ /**
+ * A callback to inform the client that pre-loading of a queue item has started
+ *
+ * @param item The queue item that the receiver has started to preload (if supported)
+ */
+ void onRemoteMediaPreloadStatusUpdated(MediaQueueItem item);
+
+ /**
+ * A callback to inform the clients that the "Play" button for the upcoming item has been
+ * clicked,
+ *
+ * @param view The view that was clicked
+ * @param upcomingItem The queue item that represents the item that is being preloaded
+ */
+ void onUpcomingPlayClicked(View view, MediaQueueItem upcomingItem);
+
+ /**
+ * A callback to inform the clients that the "Stop" button for the upcoming item has been
+ * clicked.
+ *
+ * @param view The view that was clicked
+ * @param upcomingItem The queue item that represents the item that is being preloaded
+ */
+ void onUpcomingStopClicked(View view, MediaQueueItem upcomingItem);
+
+ /**
+ * A callback to inform the client of the result of a queueing operation.
+ *
+ * @param operationId Identifier of the operation, see
+ * {@code VideoCastManager#QUEUE_OPERATION_*}
+ * @param statusCode The status code that represents the success or failure of the request.
+ * The possible value are defined in
+ * {@link com.google.android.gms.common.api.CommonStatusCodes} or
+ * {@link com.google.android.gms.cast.CastStatusCodes}.
+ * {@link com.google.android.gms.cast.CastStatusCodes#SUCCESS} signifies a successful request.
+ */
+ void onMediaQueueOperationResult(int operationId, int statusCode);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumerImpl.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumerImpl.java
new file mode 100644
index 000000000..3977edc58
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/callbacks/VideoCastConsumerImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.callbacks;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.TextTrackStyle;
+
+import android.view.View;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This is a no-ops implementation of {@link VideoCastConsumer} so that the clients that need to
+ * (partially) implement {@link VideoCastConsumer} can extend this class and only override the
+ * desired methods.
+ */
+public class VideoCastConsumerImpl extends BaseCastConsumerImpl
+ implements VideoCastConsumer {
+
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata,
+ String sessionId, boolean wasLaunched) {
+ }
+
+ @Override
+ public void onApplicationConnectionFailed(int errorCode) {
+ }
+
+ @Override
+ public void onApplicationStatusChanged(String appStatus) {
+ }
+
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ }
+
+ @Override
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ }
+
+ @Override
+ public void onRemoteMediaPlayerStatusUpdated() {
+ }
+
+ @Override
+ public void onVolumeChanged(double value, boolean isMute) {
+ }
+
+ @Override
+ public void onApplicationStopFailed(int errorCode) {
+ }
+
+ @Override
+ public void onNamespaceRemoved() {
+ }
+
+ @Override
+ public void onDataMessageSendFailed(int errorCode) {
+ }
+
+ @Override
+ public void onDataMessageReceived(String message) {
+ }
+
+ @Override
+ public void onTextTrackStyleChanged(TextTrackStyle style) {
+ }
+
+ @Override
+ public void onTextTrackEnabledChanged(boolean isEnabled) {
+ }
+
+ @Override
+ public void onTextTrackLocaleChanged(Locale locale) {
+ }
+
+ @Override
+ public void onMediaLoadResult(int statusCode) {
+ }
+
+ @Override
+ public void onMediaQueueUpdated(List queueItems, MediaQueueItem item,
+ int repeatMode, boolean shuffle) {
+ }
+
+ @Override
+ public void onRemoteMediaPreloadStatusUpdated(MediaQueueItem item) {
+ }
+
+ @Override
+ public void onUpcomingPlayClicked(View v, MediaQueueItem item) {
+ }
+
+ @Override
+ public void onUpcomingStopClicked(View view, MediaQueueItem upcomingItem) {
+ }
+
+ @Override
+ public void onMediaQueueOperationResult(int operationId, int statusCode) {
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialog.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialog.java
new file mode 100755
index 000000000..4d8e60616
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialog.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.dialog.video;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.utils.FetchBitmapTask;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.MediaRouteControllerDialog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * A custom {@link MediaRouteControllerDialog} that provides an album art, a play/pause button and
+ * the ability to take user to the target activity when the album art is tapped.
+ */
+public class VideoMediaRouteControllerDialog extends MediaRouteControllerDialog {
+
+ private static final String TAG =
+ LogUtils.makeLogTag(VideoMediaRouteControllerDialog.class);
+
+ private ImageView mIcon;
+ private ImageView mPausePlay;
+ private TextView mTitle;
+ private TextView mSubTitle;
+ private TextView mEmptyText;
+ private ProgressBar mLoading;
+ private Uri mIconUri;
+ private VideoCastManager mCastManager;
+ protected int mState;
+ private VideoCastConsumerImpl mCastConsumerImpl;
+ private Drawable mPauseDrawable;
+ private Drawable mPlayDrawable;
+ private Drawable mStopDrawable;
+ private Context mContext;
+ private View mIconContainer;
+ private View mTextContainer;
+ private FetchBitmapTask mFetchBitmap;
+
+ private int mStreamType;
+
+ public VideoMediaRouteControllerDialog(Context context, int theme) {
+ super(context, theme);
+ }
+
+ /**
+ * Creates a new VideoMediaRouteControllerDialog with the given context.
+ */
+ public VideoMediaRouteControllerDialog(Context context) {
+ super(context, R.style.CCLCastDialog);
+ try {
+ this.mContext = context;
+ mCastManager = VideoCastManager.getInstance();
+ mState = mCastManager.getPlaybackStatus();
+ mCastConsumerImpl = new VideoCastConsumerImpl() {
+
+ @Override
+ public void onRemoteMediaPlayerStatusUpdated() {
+ mState = mCastManager.getPlaybackStatus();
+ updatePlayPauseState(mState);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * com.google.android.libraries.cast.companionlibrary.cast.VideoCastConsumerImpl
+ * #onMediaChannelMetadataUpdated()
+ */
+ @Override
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ updateMetadata();
+ }
+
+ };
+ mCastManager.addVideoCastConsumer(mCastConsumerImpl);
+ mPauseDrawable = context.getResources()
+ .getDrawable(R.drawable.ic_media_route_controller_pause);
+ mPlayDrawable = context.getResources()
+ .getDrawable(R.drawable.ic_media_route_controller_play);
+ mStopDrawable = context.getResources()
+ .getDrawable(R.drawable.ic_media_route_controller_stop);
+ } catch (IllegalStateException e) {
+ LOGE(TAG, "Failed to update the content of dialog", e);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ if (mCastManager != null) {
+ mCastManager.removeVideoCastConsumer(mCastConsumerImpl);
+ mCastManager = null;
+ }
+ if (mFetchBitmap != null) {
+ mFetchBitmap.cancel(true);
+ mFetchBitmap = null;
+ }
+ super.onStop();
+ }
+
+ /*
+ * Hides/show the icon and metadata and play/pause if there is no media
+ */
+ private void hideControls(boolean hide, int resId) {
+ int visibility = hide ? View.GONE : View.VISIBLE;
+ mIcon.setVisibility(visibility);
+ mIconContainer.setVisibility(visibility);
+ mTextContainer.setVisibility(visibility);
+ mEmptyText.setText(resId == 0 ? R.string.ccl_no_media_info : resId);
+ mEmptyText.setVisibility(hide ? View.VISIBLE : View.GONE);
+ if (hide) {
+ mPausePlay.setVisibility(visibility);
+ }
+ }
+
+ private void updateMetadata() {
+ MediaInfo info;
+ try {
+ info = mCastManager.getRemoteMediaInformation();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ hideControls(true, R.string.ccl_failed_no_connection_short);
+ return;
+ }
+ if (info == null) {
+ hideControls(true, R.string.ccl_no_media_info);
+ return;
+ }
+ mStreamType = info.getStreamType();
+ hideControls(false, 0);
+ MediaMetadata mm = info.getMetadata();
+ mTitle.setText(mm.getString(MediaMetadata.KEY_TITLE));
+ mSubTitle.setText(mm.getString(MediaMetadata.KEY_SUBTITLE));
+ setIcon(mm.hasImages() ? mm.getImages().get(0).getUrl() : null);
+ }
+
+ public void setIcon(Uri uri) {
+ if (mIconUri != null && mIconUri.equals(uri)) {
+ return;
+ }
+ mIconUri = uri;
+ if (uri == null) {
+ Bitmap bm = BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.album_art_placeholder);
+ mIcon.setImageBitmap(bm);
+ return;
+ }
+ if (mFetchBitmap != null) {
+ mFetchBitmap.cancel(true);
+ }
+
+ mFetchBitmap = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ mIcon.setImageBitmap(bitmap);
+ if (this == mFetchBitmap) {
+ mFetchBitmap = null;
+ }
+ }
+ };
+
+ mFetchBitmap.execute(mIconUri);
+ }
+
+ private void updatePlayPauseState(int state) {
+ if (mPausePlay != null) {
+ switch (state) {
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ mPausePlay.setImageDrawable(getPauseStopDrawable());
+ adjustControlsVisibility(true);
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ mPausePlay.setImageDrawable(mPlayDrawable);
+ adjustControlsVisibility(true);
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ mPausePlay.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+
+ if (mState == MediaStatus.PLAYER_STATE_IDLE
+ && mCastManager.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) {
+ hideControls(true, R.string.ccl_no_media_info);
+ } else {
+ switch (mStreamType) {
+ case MediaInfo.STREAM_TYPE_BUFFERED:
+ mPausePlay.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ break;
+ case MediaInfo.STREAM_TYPE_LIVE:
+ int idleReason = mCastManager.getIdleReason();
+ if (idleReason == MediaStatus.IDLE_REASON_CANCELED) {
+ mPausePlay.setImageDrawable(mPlayDrawable);
+ adjustControlsVisibility(true);
+ } else {
+ mPausePlay.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ }
+ break;
+ }
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ adjustControlsVisibility(false);
+ break;
+ default:
+ mPausePlay.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ }
+ }
+ }
+
+ private Drawable getPauseStopDrawable() {
+ switch (mStreamType) {
+ case MediaInfo.STREAM_TYPE_BUFFERED:
+ return mPauseDrawable;
+ case MediaInfo.STREAM_TYPE_LIVE:
+ return mStopDrawable;
+ default:
+ return mPauseDrawable;
+ }
+ }
+
+ private void setLoadingVisibility(boolean show) {
+ mLoading.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ private void adjustControlsVisibility(boolean showPlayPause) {
+ int visible = showPlayPause ? View.VISIBLE : View.INVISIBLE;
+ mPausePlay.setVisibility(visible);
+ setLoadingVisibility(!showPlayPause);
+ }
+
+ /**
+ * Initializes this dialog's set of playback buttons and adds click listeners.
+ */
+ @Override
+ public View onCreateMediaControlView(Bundle savedInstanceState) {
+ LayoutInflater inflater = getLayoutInflater();
+ View controls = inflater.inflate(R.layout.custom_media_route_controller_controls_dialog,
+ null);
+
+ loadViews(controls);
+ mState = mCastManager.getPlaybackStatus();
+ updateMetadata();
+ updatePlayPauseState(mState);
+ setUpCallbacks();
+ return controls;
+ }
+
+ private void setUpCallbacks() {
+
+ mPausePlay.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mCastManager == null) {
+ return;
+ }
+ try {
+ adjustControlsVisibility(false);
+ mCastManager.togglePlayback();
+ } catch (CastException e) {
+ adjustControlsVisibility(true);
+ LOGE(TAG, "Failed to toggle playback", e);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ adjustControlsVisibility(true);
+ LOGE(TAG, "Failed to toggle playback due to network issues", e);
+ }
+ }
+ });
+
+ mIcon.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showTargetActivity();
+ }
+
+ });
+
+ mTextContainer.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showTargetActivity();
+ }
+
+ });
+ }
+
+ private void showTargetActivity() {
+ if (mCastManager != null
+ && mCastManager.getTargetActivity() != null) {
+ try {
+ mCastManager.onTargetActivityInvoked(mContext);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to start the target activity due to network issues", e);
+ }
+ cancel();
+ }
+ }
+
+ private void loadViews(View controls) {
+ mIcon = (ImageView) controls.findViewById(R.id.iconView);
+ mIconContainer = controls.findViewById(R.id.iconContainer);
+ mTextContainer = controls.findViewById(R.id.textContainer);
+ mPausePlay = (ImageView) controls.findViewById(R.id.playPauseView);
+ mTitle = (TextView) controls.findViewById(R.id.titleView);
+ mSubTitle = (TextView) controls.findViewById(R.id.subTitleView);
+ mLoading = (ProgressBar) controls.findViewById(R.id.loadingView);
+ mEmptyText = (TextView) controls.findViewById(R.id.emptyView);
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialogFragment.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialogFragment.java
new file mode 100755
index 000000000..baa6c8d61
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteControllerDialogFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.dialog.video;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+
+/**
+ * An extension of MediaRouteControllerDialogFragment which contains a
+ * VideoMediaRouteControllerDialog.
+ */
+public class VideoMediaRouteControllerDialogFragment extends MediaRouteControllerDialogFragment {
+
+ @Override
+ public VideoMediaRouteControllerDialog onCreateControllerDialog(
+ Context context, Bundle savedInstanceState) {
+ VideoMediaRouteControllerDialog customControllerDialog
+ = new VideoMediaRouteControllerDialog(context);
+ customControllerDialog.setVolumeControlEnabled(false);
+ return customControllerDialog;
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteDialogFactory.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteDialogFactory.java
new file mode 100644
index 000000000..c8fbeb4cf
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/dialog/video/VideoMediaRouteDialogFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.dialog.video;
+
+import android.support.v7.app.MediaRouteDialogFactory;
+
+/**
+ * A factory for the MediaRoute Dialog.
+ */
+public class VideoMediaRouteDialogFactory extends MediaRouteDialogFactory {
+
+ @Override
+ public VideoMediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
+ return new VideoMediaRouteControllerDialogFragment();
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/CastException.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/CastException.java
new file mode 100644
index 000000000..a25a6fde6
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/CastException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.exceptions;
+
+import android.content.Context;
+
+/**
+ * A generic exception that a method can throw to indicate an issue related to the cast operation.
+ * More specific issues will be thrown separately.
+ */
+public class CastException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public CastException() {
+ }
+
+ public CastException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public CastException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public CastException(Context ctx, int resId) {
+ super(ctx.getResources().getString(resId));
+ }
+
+ public CastException(Context ctx, int resId, Exception e) {
+ super(ctx.getResources().getString(resId), e);
+ }
+
+ public CastException(Throwable throwable) {
+ super(throwable);
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/NoConnectionException.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/NoConnectionException.java
new file mode 100644
index 000000000..55ee933f9
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/NoConnectionException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.exceptions;
+
+import java.io.IOException;
+
+/**
+ * Is used to indicate that the connectivity to the cast device is not there. User needs to take
+ * manual steps to fix this issue.
+ */
+@SuppressWarnings("serial")
+public class NoConnectionException extends IOException {
+
+ public NoConnectionException() {
+ }
+
+ public NoConnectionException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public NoConnectionException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/OnFailedListener.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/OnFailedListener.java
new file mode 100644
index 000000000..34d44c36d
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/OnFailedListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.exceptions;
+
+/**
+ * An interface for reporting back errors in an asynchronous way.
+ */
+public interface OnFailedListener {
+
+ /**
+ * This method is called to report a failure.
+ *
+ * @param resourceId The resource that has a textual description of the problem
+ * @param statusCode An additional integer to further specify the error. Value
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager#NO_STATUS_CODE} //NOLINT
+ * would be interpreted as no status code available.
+ */
+ void onFailed(int resourceId, int statusCode);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/TransientNetworkDisconnectionException.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/TransientNetworkDisconnectionException.java
new file mode 100644
index 000000000..a794177c5
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/exceptions/TransientNetworkDisconnectionException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.exceptions;
+
+import java.io.IOException;
+
+/**
+ * Is used to indicate a transient disconnection that may be corrected automatically by the
+ * framework.
+ */
+@SuppressWarnings("serial")
+public class TransientNetworkDisconnectionException extends IOException {
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthListener.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthListener.java
new file mode 100644
index 000000000..720692891
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.player;
+
+import com.google.android.gms.cast.MediaInfo;
+
+import org.json.JSONObject;
+
+/**
+ * A public interface that provides callbacks for the {@link MediaAuthService} to communicate with
+ * the framework
+ */
+public interface MediaAuthListener {
+
+ /**
+ * Called when MediaAuthService has successfully obtained a result.
+ *
+ * @param status Provides the status of result, will be one of
+ * {@link MediaAuthStatus#AUTHORIZED} or
+ * {@link MediaAuthStatus#NOT_AUTHORIZED}
+ * @param info The fully populated {@link MediaInfo} that is obtained through authorization.
+ * @param message If authorization was not granted, then an optional message can be provided
+ * to be presented to the user. If no message is provided, it will be silently ignored.
+ * Implementers have to make sure the message is localized.
+ * @param startPoint The position in video to start the playback at (in milliseconds)
+ * @param customData Optional {@link org.json.JSONObject}
+ */
+ void onAuthResult(MediaAuthStatus status, MediaInfo info, String message,
+ int startPoint, JSONObject customData);
+
+ /**
+ * Called when MediaAuthService returns with a failure message due to some issues such as
+ * network, backend issues, etc.
+ *
+ * @param failureMessage The message stating the reason for failure. This message should be
+ * localized.
+ */
+ void onAuthFailure(String failureMessage);
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthService.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthService.java
new file mode 100644
index 000000000..f549af925
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthService.java
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.player;
+
+import com.google.android.gms.cast.MediaInfo;
+
+/**
+ * A public interface if an application requires a pre-authorization of a media, prior to its
+ * playback. Applications should implement this interface when they want to obtain a
+ * pre-authorization prior to calling the {@link VideoCastControllerActivity}. The implementation
+ * should prepare the stage for its own out-of-bound process but should not start that till the
+ * {@link MediaAuthService#startAuthorization()} is called by the CCL library. Applications should
+ * provide a timeout limit to make sure that this out-of-bound process is completed within a
+ * reasonable period of time.
+ *
+ * Framework passes an {@link MediaAuthListener} to the implementation of this interface to provide
+ * a way for the implementation to callback to the framework with its results. When the
+ * authorization process ends, the implementation has to call
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthListener#onAuthResult(MediaAuthStatus, MediaInfo, String, int, org.json.JSONObject)} // NOLINT
+ * with the relevant results; whether the authorization was granted or rejected. If, however, the
+ * process encounters an unrecoverable error, it has to call the
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthListener#onAuthFailure(String)} // NOLINT
+ * callback of the {@link MediaAuthListener} to inform the framework.
+ *
+ * If the library decides to to interrupt the authorization process (say, a user decides to
+ * interrupt the process or if it times out), it will call
+ * {@link #abortAuthorization(MediaAuthStatus)} and provides a reason. Implementation has to make
+ * sure that it will not call any of the framework callbacks after it has received an abort message.
+ *
+ * Since authorization process can be involved and may require network access, the
+ * {@link MediaAuthService#startAuthorization()} method is called on a non-UI thread. Callbacks into
+ * the framework can happen on or off the UI thread.
+ */
+public interface MediaAuthService {
+
+ /**
+ * Starts the authorization process. Before this call, it is assumed that the implementor has
+ * all the information required to perform the authorization task. This is where the dynamic
+ * life cycle of this class starts.
+ */
+ public void startAuthorization();
+
+ /**
+ * Registers an {@link MediaAuthListener} listener to be notified when the authentication
+ * service has obtained its result. To remove a previously set listener, pass a
+ * null
argument.
+ */
+ public void setMediaAuthListener(MediaAuthListener listener);
+
+ /**
+ * Returns the current {@link MediaInfo} object that is the subject of authorization. At a
+ * minimum, it is expected to have images for the media at any stage.
+ */
+ public MediaInfo getMediaInfo();
+
+ /**
+ * In pending state, implementors can provide an optional localized message to be shown to the
+ * user. If null
is returned, no message will be shown to the user.
+ */
+ public String getPendingMessage();
+
+ /**
+ * Returns the current status of the service.
+ */
+ public MediaAuthStatus getStatus();
+
+ /**
+ * Returns the length of time within which the library expects to have heard back from the
+ * authorization service. If it doesn't, it will call
+ * {@link #abortAuthorization(MediaAuthStatus)}.
+ *
+ * @return Timeout in milliseconds
+ */
+ public long getTimeout();
+
+ /**
+ * If authorization times out or user cancels the authorization process, this method will be
+ * called.
+ *
+ * @param abortReason One of the {@code MediaAuthStatus#ABORT_*} reasons
+ */
+ public void abortAuthorization(MediaAuthStatus abortReason);
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthStatus.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthStatus.java
new file mode 100644
index 000000000..c0c7ab39a
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/MediaAuthStatus.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.player;
+
+/**
+ * An enum to enumerate various states of the authentication process used in
+ * {@link MediaAuthService}
+ */
+public enum MediaAuthStatus {
+ /* Service has not started yet */
+ NOT_STARTED,
+
+ /* Service is running but no results is available yet */
+ PENDING,
+
+ /* Service has finished its query and results are available */
+ FINISHED,
+
+ /* Service has finished and user was authorized */
+ AUTHORIZED,
+
+ /* Service has finished but user was not authorized */
+ NOT_AUTHORIZED,
+
+ /* Timeout has reached with no result */
+ TIMED_OUT,
+
+ /* User triggered abort */
+ CANCELED_BY_USER,
+
+ /* Abort due to an unknown issue */
+ ABORT_UNKNOWN;
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/OnVideoCastControllerListener.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/OnVideoCastControllerListener.java
new file mode 100644
index 000000000..4c831ffed
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/OnVideoCastControllerListener.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.player;
+
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.tracks.OnTracksSelectedListener;
+
+import android.view.View;
+import android.widget.SeekBar;
+
+/**
+ * An interface that enables an alternative implementation of
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerFragment}. // NOLINT
+ */
+public interface OnVideoCastControllerListener extends OnTracksSelectedListener {
+
+ /**
+ * Called when seeking is stopped by user.
+ */
+ void onStopTrackingTouch(SeekBar seekBar);
+
+ /**
+ * Called when seeking starts by user
+ */
+ void onStartTrackingTouch(SeekBar seekBar);
+
+ /**
+ * Called while seeking is happening by the user
+ */
+ void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser);
+
+ /**
+ * Notification that user has clicked on the Play/Pause button
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws CastException
+ */
+ void onPlayPauseClicked(View v) throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException;
+
+ /**
+ * Called when a configuration change happens (for example device is rotated)
+ */
+ void onConfigurationChanged();
+
+ /**
+ * Called when user clicks on the Skip Next button
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ void onSkipNextClicked(View v) throws TransientNetworkDisconnectionException,
+ NoConnectionException;
+
+ /**
+ * Called when user clicks on the Skip Previous button
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ */
+ void onSkipPreviousClicked(View v)
+ throws TransientNetworkDisconnectionException, NoConnectionException;
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastController.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastController.java
new file mode 100644
index 000000000..ee7f415ee
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastController.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.player;
+
+import com.google.android.gms.cast.MediaStatus;
+
+import android.graphics.Bitmap;
+
+/**
+ * An interface that can be used to display a remote controller for the video that is playing on
+ * the cast device.
+ */
+public interface VideoCastController {
+
+ int CC_ENABLED = 1;
+ int CC_DISABLED = 2;
+ int CC_HIDDEN = 3;
+
+ int NEXT_PREV_VISIBILITY_POLICY_HIDDEN = 1;
+ int NEXT_PREV_VISIBILITY_POLICY_DISABLED = 2;
+ int NEXT_PREV_VISIBILITY_POLICY_ALWAYS = 3;
+
+ /**
+ * Sets the bitmap for the album art
+ */
+ void setImage(Bitmap bitmap);
+
+ /**
+ * Sets the title
+ */
+ void setTitle(String text);
+
+ /**
+ * Sets the subtitle
+ */
+ void setSubTitle(String text);
+
+ /**
+ * Sets the playback state, and the idleReason (this is only used when the state is idle).
+ * Values that can be passed to this method are from {@link MediaStatus}
+ */
+ void setPlaybackStatus(int state);
+
+ /**
+ * Assigns a {@link OnVideoCastControllerListener} listener to be notified of the changes in
+ * the {@link VideoCastController}
+ */
+ void setOnVideoCastControllerChangedListener(OnVideoCastControllerListener listener);
+
+ /**
+ * Sets the type of stream. {@code streamType} can be
+ * {@link com.google.android.gms.cast.MediaInfo#STREAM_TYPE_LIVE} or
+ * {@link com.google.android.gms.cast.MediaInfo#STREAM_TYPE_BUFFERED}
+ */
+ void setStreamType(int streamType);
+
+ /**
+ * Updates the position and total duration for the seekbar that presents the progress of media.
+ * Both of these need to be provided in milliseconds.
+ */
+ void updateSeekbar(int position, int duration);
+
+ /**
+ * Adjust the visibility of control widgets on the UI.
+ */
+ void updateControllersStatus(boolean enabled);
+
+ /**
+ * Can be used to show a loading icon during processes that could take time.
+ */
+ void showLoading(boolean visible);
+
+ /**
+ * Closes the activity related to the UI.
+ */
+ void closeActivity();
+
+ /**
+ * This can be used to adjust the UI for playback of live versus pre-recorded streams. Certain
+ * UI widgets may need to be updated when playing a live stream. For example, the progress bar
+ * may not be needed for a live stream while it may be required for a pre-recorded stream.
+ */
+ void adjustControllersForLiveStream(boolean isLive);
+
+ /**
+ * Updates the visual status of the Closed Caption icon. Possible states are provided by
+ * CC_ENABLED, CC_DISABLED, CC_HIDDEN
+ */
+ void setClosedCaptionState(int status);
+
+ /**
+ * Called when the queue items are updated and provides information about the updated size of
+ * the queue and the position of the current item in the queue. This can be useful to update
+ * the UI if the relative position of the current item is relevant (e.g. to disable or hide
+ * "skip next/prev" buttons).
+ */
+ void onQueueItemsUpdated(int queueLength, int position);
+
+ /**
+ * Sets the policy for the visibility/status of the Skip Next/Prev buttons. The policy declares
+ * what should the visibility or status of these buttons be when the position of the current
+ * item is at the edges of the queue. For example, if the current item is the last item in the
+ * queue, what should be the visibility or status of the "Skip Next" button. Available policies
+ * are:
+ *
+ * {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_ALWAYS}: always show the button
+ *
+ * {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_DISABLED}: disable the button
+ *
+ * {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_HIDDEN}: hide the button
+ *
+ * The default behavior is {@link VideoCastController#NEXT_PREV_VISIBILITY_POLICY_DISABLED}
+ */
+ void setNextPreviousVisibilityPolicy(int policy);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerActivity.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerActivity.java
new file mode 100644
index 000000000..9f78f6634
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerActivity.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2014 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.libraries.cast.companionlibrary.cast.player;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions
+ .TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.tracks.ui.TracksChooserDialog;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+import com.google.android.libraries.cast.companionlibrary.widgets.MiniController;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+/**
+ * This class provides an {@link android.app.Activity} that clients can easily add to their
+ * applications to provide an out-of-the-box remote player when a video is casting to a cast device.
+ * {@link com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager} can manage the
+ * lifecycle and presentation of this activity.
+ *
+ * This activity provides a number of controllers for managing the playback of the remote content:
+ * play/pause (or play/stop when a live stream is used) and seekbar (for non-live streams).
+ *
+ * Clients who need to perform a pre-authorization process for playback can register a
+ * {@link MediaAuthListener} by calling
+ * {@link VideoCastManager#startVideoCastControllerActivity(android.content.Context, MediaAuthService)}
+ * In that case, this activity manages starting the {@link MediaAuthService} and will register a
+ * listener to handle the result.
+ */
+public class VideoCastControllerActivity extends AppCompatActivity implements
+ VideoCastController {
+
+ private static final String TAG = LogUtils
+ .makeLogTag(VideoCastControllerActivity.class);
+ public static final String TASK_TAG = "task";
+ public static final String DIALOG_TAG = "dialog";
+ private VideoCastManager mCastManager;
+ private View mPageView;
+ private ImageButton mPlayPause;
+ private TextView mLiveText;
+ private TextView mStart;
+ private TextView mEnd;
+ private SeekBar mSeekbar;
+ private TextView mLine2;
+ private ProgressBar mLoading;
+ private double mVolumeIncrement;
+ private View mControllers;
+ private Drawable mPauseDrawable;
+ private Drawable mPlayDrawable;
+ private Drawable mStopDrawable;
+ private OnVideoCastControllerListener mListener;
+ private int mStreamType;
+ private ImageButton mClosedCaptionIcon;
+ private ImageButton mSkipNext;
+ private ImageButton mSkipPrevious;
+ private View mPlaybackControls;
+ private Toolbar mToolbar;
+ private int mNextPreviousVisibilityPolicy
+ = VideoCastController.NEXT_PREV_VISIBILITY_POLICY_DISABLED;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.cast_activity);
+ loadAndSetupViews();
+ mCastManager = VideoCastManager.getInstance();
+ mVolumeIncrement = mCastManager.getVolumeStep();
+
+ Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ finish();
+ return;
+ }
+
+ setUpActionBar();
+
+ FragmentManager fm = getSupportFragmentManager();
+ VideoCastControllerFragment videoCastControllerFragment
+ = (VideoCastControllerFragment) fm.findFragmentByTag(TASK_TAG);
+
+ // if fragment is null, it means this is the first time, so create it
+ if (videoCastControllerFragment == null) {
+ videoCastControllerFragment = VideoCastControllerFragment
+ .newInstance(extras);
+ fm.beginTransaction().add(videoCastControllerFragment, TASK_TAG).commit();
+ setOnVideoCastControllerChangedListener(videoCastControllerFragment);
+ } else {
+ setOnVideoCastControllerChangedListener(videoCastControllerFragment);
+ mListener.onConfigurationChanged();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.cast_player_menu, menu);
+ mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ return mCastManager.onDispatchVolumeKeyEvent(event, mVolumeIncrement) || super
+ .dispatchKeyEvent(event);
+ }
+
+ private void loadAndSetupViews() {
+ mPauseDrawable = getResources().getDrawable(R.drawable.ic_pause_circle_white_80dp);
+ mPlayDrawable = getResources().getDrawable(R.drawable.ic_play_circle_white_80dp);
+ mStopDrawable = getResources().getDrawable(R.drawable.ic_stop_circle_white_80dp);
+ mPageView = findViewById(R.id.pageview);
+ mPlayPause = (ImageButton) findViewById(R.id.play_pause_toggle);
+ mLiveText = (TextView) findViewById(R.id.live_text);
+ mStart = (TextView) findViewById(R.id.start_text);
+ mEnd = (TextView) findViewById(R.id.end_text);
+ mSeekbar = (SeekBar) findViewById(R.id.seekbar);
+ mLine2 = (TextView) findViewById(R.id.textview2);
+ mLoading = (ProgressBar) findViewById(R.id.progressbar1);
+ mControllers = findViewById(R.id.controllers);
+ mClosedCaptionIcon = (ImageButton) findViewById(R.id.cc);
+ mSkipNext = (ImageButton) findViewById(R.id.next);
+ mSkipPrevious = (ImageButton) findViewById(R.id.previous);
+ mPlaybackControls = findViewById(R.id.playback_controls);
+ ((MiniController) findViewById(R.id.miniController1)).setCurrentVisibility(false);
+ setClosedCaptionState(CC_DISABLED);
+ mPlayPause.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ try {
+ mListener.onPlayPauseClicked(v);
+ } catch (TransientNetworkDisconnectionException e) {
+ LOGE(TAG, "Failed to toggle playback due to temporary network issue", e);
+ Utils.showToast(VideoCastControllerActivity.this,
+ R.string.ccl_failed_no_connection_trans);
+ } catch (NoConnectionException e) {
+ LOGE(TAG, "Failed to toggle playback due to network issues", e);
+ Utils.showToast(VideoCastControllerActivity.this,
+ R.string.ccl_failed_no_connection);
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to toggle playback due to other issues", e);
+ Utils.showToast(VideoCastControllerActivity.this,
+ R.string.ccl_failed_perform_action);
+ }
+ }
+ });
+
+ mSeekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ try {
+ if (mListener != null) {
+ mListener.onStopTrackingTouch(seekBar);
+ }
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to complete seek", e);
+ finish();
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ try {
+ if (mListener != null) {
+ mListener.onStartTrackingTouch(seekBar);
+ }
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to start seek", e);
+ finish();
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser) {
+ mStart.setText(Utils.formatMillis(progress));
+ try {
+ if (mListener != null) {
+ mListener.onProgressChanged(seekBar, progress, fromUser);
+ }
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to set the progress result", e);
+ }
+ }
+ });
+
+ mClosedCaptionIcon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ showTracksChooserDialog();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to get the media", e);
+ }
+ }
+ });
+
+ mSkipNext.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ mListener.onSkipNextClicked(v);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to move to the next item in the queue", e);
+ }
+ }
+ });
+
+ mSkipPrevious.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ mListener.onSkipPreviousClicked(v);
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to move to the previous item in the queue", e);
+ }
+ }
+ });
+ }
+
+ private void showTracksChooserDialog()
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag(DIALOG_TAG);
+ if (prev != null) {
+ transaction.remove(prev);
+ }
+ transaction.addToBackStack(null);
+
+ // Create and show the dialog.
+ TracksChooserDialog dialogFragment = TracksChooserDialog
+ .newInstance(mCastManager.getRemoteMediaInformation());
+ dialogFragment.show(transaction, DIALOG_TAG);
+ }
+
+ private void setUpActionBar() {
+ mToolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(mToolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public void showLoading(boolean visible) {
+ mLoading.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @Override
+ public void adjustControllersForLiveStream(boolean isLive) {
+ int visibility = isLive ? View.INVISIBLE : View.VISIBLE;
+ mLiveText.setVisibility(isLive ? View.VISIBLE : View.INVISIBLE);
+ mStart.setVisibility(visibility);
+ mEnd.setVisibility(visibility);
+ mSeekbar.setVisibility(visibility);
+ }
+
+ @Override
+ public void setClosedCaptionState(int status) {
+ switch (status) {
+ case CC_ENABLED:
+ mClosedCaptionIcon.setVisibility(View.VISIBLE);
+ mClosedCaptionIcon.setEnabled(true);
+ break;
+ case CC_DISABLED:
+ mClosedCaptionIcon.setVisibility(View.VISIBLE);
+ mClosedCaptionIcon.setEnabled(false);
+ break;
+ case CC_HIDDEN:
+ mClosedCaptionIcon.setVisibility(View.GONE);
+ break;
+ default:
+ LOGE(TAG, "setClosedCaptionState(): Invalid state requested: " + status);
+ }
+ }
+
+ @Override
+ public void onQueueItemsUpdated(int queueLength, int position) {
+ boolean prevAvailable = position > 0;
+ boolean nextAvailable = position < queueLength - 1;
+ switch(mNextPreviousVisibilityPolicy) {
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_HIDDEN:
+ if (nextAvailable) {
+ mSkipNext.setVisibility(View.VISIBLE);
+ mSkipNext.setEnabled(true);
+ } else {
+ mSkipNext.setVisibility(View.INVISIBLE);
+ }
+ if (prevAvailable) {
+ mSkipPrevious.setVisibility(View.VISIBLE);
+ mSkipPrevious.setEnabled(true);
+ } else {
+ mSkipPrevious.setVisibility(View.INVISIBLE);
+ }
+ break;
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_ALWAYS:
+ mSkipNext.setVisibility(View.VISIBLE);
+ mSkipNext.setEnabled(true);
+ mSkipPrevious.setVisibility(View.VISIBLE);
+ mSkipPrevious.setEnabled(true);
+ break;
+ case VideoCastController.NEXT_PREV_VISIBILITY_POLICY_DISABLED:
+ if (nextAvailable) {
+ mSkipNext.setVisibility(View.VISIBLE);
+ mSkipNext.setEnabled(true);
+ } else {
+ mSkipNext.setVisibility(View.VISIBLE);
+ mSkipNext.setEnabled(false);
+ }
+ if (prevAvailable) {
+ mSkipPrevious.setVisibility(View.VISIBLE);
+ mSkipPrevious.setEnabled(true);
+ } else {
+ mSkipPrevious.setVisibility(View.VISIBLE);
+ mSkipPrevious.setEnabled(false);
+ }
+ break;
+ default:
+ LOGE(TAG, "onQueueItemsUpdated(): Invalid NextPreviousPolicy has been set");
+ }
+ }
+
+ @Override
+ public void setPlaybackStatus(int state) {
+ LOGD(TAG, "setPlaybackStatus(): state = " + state);
+ switch (state) {
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ mLoading.setVisibility(View.INVISIBLE);
+ mPlaybackControls.setVisibility(View.VISIBLE);
+ if (mStreamType == MediaInfo.STREAM_TYPE_LIVE) {
+ mPlayPause.setImageDrawable(mStopDrawable);
+ } else {
+ mPlayPause.setImageDrawable(mPauseDrawable);
+ }
+
+ mLine2.setText(getString(R.string.ccl_casting_to_device,
+ mCastManager.getDeviceName()));
+ mControllers.setVisibility(View.VISIBLE);
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ mControllers.setVisibility(View.VISIBLE);
+ mLoading.setVisibility(View.INVISIBLE);
+ mPlaybackControls.setVisibility(View.VISIBLE);
+ mPlayPause.setImageDrawable(mPlayDrawable);
+ mLine2.setText(getString(R.string.ccl_casting_to_device,
+ mCastManager.getDeviceName()));
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ mPlaybackControls.setVisibility(View.INVISIBLE);
+ mLoading.setVisibility(View.VISIBLE);
+ mLine2.setText(getString(R.string.ccl_loading));
+ break;
+ default:
+ }
+ }
+
+ @Override
+ public void updateSeekbar(int position, int duration) {
+ mSeekbar.setProgress(position);
+ mSeekbar.setMax(duration);
+ mStart.setText(Utils.formatMillis(position));
+ mEnd.setText(Utils.formatMillis(duration));
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setImage(Bitmap bitmap) {
+ if (bitmap != null) {
+ if (mPageView instanceof ImageView) {
+ ((ImageView) mPageView).setImageBitmap(bitmap);
+ } else {
+ mPageView.setBackgroundDrawable(new BitmapDrawable(getResources(), bitmap));
+ }
+ }
+ }
+
+ @Override
+ public void setTitle(String text) {
+ mToolbar.setTitle(text);
+ }
+
+ @Override
+ public void setSubTitle(String text) {
+ mLine2.setText(text);
+ }
+
+ @Override
+ public void setOnVideoCastControllerChangedListener(OnVideoCastControllerListener listener) {
+ if (listener != null) {
+ mListener = listener;
+ }
+ }
+
+ @Override
+ public void setStreamType(int streamType) {
+ this.mStreamType = streamType;
+ }
+
+ @Override
+ public void updateControllersStatus(boolean enabled) {
+ mControllers.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ if (enabled) {
+ adjustControllersForLiveStream(mStreamType == MediaInfo.STREAM_TYPE_LIVE);
+ }
+ }
+
+ @Override
+ public void closeActivity() {
+ finish();
+ }
+
+ @Override // from VideoCastController
+ public void setNextPreviousVisibilityPolicy(int policy) {
+ mNextPreviousVisibilityPolicy = policy;
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerFragment.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerFragment.java
new file mode 100644
index 000000000..196162c89
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/player/VideoCastControllerFragment.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2014 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.libraries.cast.companionlibrary.cast.player;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.gms.cast.MediaTrack;
+import com.google.android.gms.cast.RemoteMediaPlayer;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.MediaQueue;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions
+ .TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.utils.FetchBitmapTask;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.SeekBar;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A fragment that provides a mechanism to retain the state and other needed objects for
+ * {@link VideoCastControllerActivity} (or more generally, for any class implementing
+ * {@link VideoCastController} interface). This can come very handy when set up of that activity
+ * allows for a configuration changes. Most of the logic required for
+ * {@link VideoCastControllerActivity} is maintained in this fragment to enable application
+ * developers provide a different implementation, if desired.
+ *
+ * This fragment also provides an implementation of {@link MediaAuthListener} which can be useful
+ * if a pre-authorization is required for playback of a media.
+ */
+public class VideoCastControllerFragment extends Fragment implements
+ OnVideoCastControllerListener, MediaAuthListener {
+
+ private static final String EXTRAS = "extras";
+ private static final String TAG = LogUtils.makeLogTag(VideoCastControllerFragment.class);
+ private MediaInfo mSelectedMedia;
+ private VideoCastManager mCastManager;
+ private MediaAuthService mMediaAuthService;
+ private Thread mAuthThread;
+ private Timer mMediaAuthTimer;
+ private Handler mHandler;
+ protected boolean mAuthSuccess = true;
+ private VideoCastController mCastController;
+ private FetchBitmapTask mImageAsyncTask;
+ private Timer mSeekbarTimer;
+ private int mPlaybackState;
+ private MyCastConsumer mCastConsumer;
+ private OverallState mOverallState = OverallState.UNKNOWN;
+ private UrlAndBitmap mUrlAndBitmap;
+ private static boolean sDialogCanceled = false;
+ private boolean mIsFresh = true;
+ private MediaStatus mMediaStatus;
+
+ private enum OverallState {
+ AUTHORIZING, PLAYBACK, UNKNOWN
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ sDialogCanceled = false;
+ mCastController = (VideoCastController) activity;
+ mHandler = new Handler();
+ mCastManager = VideoCastManager.getInstance();
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (mCastManager.getPreferenceAccessor()
+ .getBooleanFromPreference(VideoCastManager.PREFS_KEY_IMMERSIVE_MODE, true)) {
+ setImmersive();
+ }
+ mCastConsumer = new MyCastConsumer();
+ Bundle bundle = getArguments();
+ if (bundle == null) {
+ return;
+ }
+ Bundle extras = bundle.getBundle(EXTRAS);
+ Bundle mediaWrapper = extras.getBundle(VideoCastManager.EXTRA_MEDIA);
+
+ // Retain this fragment across configuration changes.
+ setRetainInstance(true);
+ mCastManager.addTracksSelectedListener(this);
+ boolean explicitStartActivity = mCastManager.getPreferenceAccessor()
+ .getBooleanFromPreference(VideoCastManager.PREFS_KEY_START_ACTIVITY, false);
+ if (explicitStartActivity) {
+ mIsFresh = true;
+ }
+ mCastManager.getPreferenceAccessor().saveBooleanToPreference(
+ VideoCastManager.PREFS_KEY_START_ACTIVITY, false);
+ int nextPreviousVisibilityPolicy = mCastManager.getPreferenceAccessor()
+ .getIntFromPreference(VideoCastManager.PREFS_KEY_NEXT_PREV_POLICY,
+ VideoCastController.NEXT_PREV_VISIBILITY_POLICY_DISABLED);
+ mCastController.setNextPreviousVisibilityPolicy(nextPreviousVisibilityPolicy);
+ if (extras.getBoolean(VideoCastManager.EXTRA_HAS_AUTH)) {
+ if (mIsFresh) {
+ mOverallState = OverallState.AUTHORIZING;
+ mMediaAuthService = mCastManager.getMediaAuthService();
+ handleMediaAuthTask(mMediaAuthService);
+ showImage(Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1));
+ }
+ } else if (mediaWrapper != null) {
+ mOverallState = OverallState.PLAYBACK;
+ boolean shouldStartPlayback = extras.getBoolean(VideoCastManager.EXTRA_SHOULD_START);
+ String customDataStr = extras.getString(VideoCastManager.EXTRA_CUSTOM_DATA);
+ JSONObject customData = null;
+ if (!TextUtils.isEmpty(customDataStr)) {
+ try {
+ customData = new JSONObject(customDataStr);
+ } catch (JSONException e) {
+ LOGE(TAG, "Failed to unmarshalize custom data string: customData="
+ + customDataStr, e);
+ }
+ }
+ MediaInfo info = Utils.bundleToMediaInfo(mediaWrapper);
+ int startPoint = extras.getInt(VideoCastManager.EXTRA_START_POINT, 0);
+ onReady(info, shouldStartPlayback && explicitStartActivity, startPoint, customData);
+ }
+ }
+
+ /*
+ * Starts a background thread for starting the Auth Service
+ */
+ private void handleMediaAuthTask(final MediaAuthService authService) {
+ mCastController.showLoading(true);
+ if (authService == null) {
+ return;
+ }
+ mCastController.setSubTitle(authService.getPendingMessage() != null
+ ? authService.getPendingMessage() : "");
+ mAuthThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ authService.setMediaAuthListener(VideoCastControllerFragment.this);
+ authService.startAuthorization();
+ }
+ });
+ mAuthThread.start();
+
+ // start a timeout timer; we don't want authorization process to take too long
+ mMediaAuthTimer = new Timer();
+ mMediaAuthTimer.schedule(new MediaAuthServiceTimerTask(mAuthThread),
+ authService.getTimeout());
+ }
+
+ /*
+ * A TimerTask that will be called when the auth timer expires
+ */
+ class MediaAuthServiceTimerTask extends TimerTask {
+
+ private final Thread mThread;
+
+ public MediaAuthServiceTimerTask(Thread thread) {
+ this.mThread = thread;
+ }
+
+ @Override
+ public void run() {
+ if (mThread != null) {
+ LOGD(TAG, "Timer is expired, going to interrupt the thread");
+ mThread.interrupt();
+ mHandler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ mCastController.showLoading(false);
+ showErrorDialog(getString(R.string.ccl_failed_authorization_timeout));
+ mAuthSuccess = false;
+ if ((mMediaAuthService != null)
+ && (mMediaAuthService.getStatus() == MediaAuthStatus.PENDING)) {
+ mMediaAuthService.abortAuthorization(MediaAuthStatus.TIMED_OUT);
+ }
+ }
+ });
+
+ }
+ }
+
+ }
+
+ private class MyCastConsumer extends VideoCastConsumerImpl {
+
+ @Override
+ public void onDisconnected() {
+ mCastController.closeActivity();
+ }
+
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ mCastController.closeActivity();
+ }
+
+ @Override
+ public void onRemoteMediaPlayerMetadataUpdated() {
+ try {
+ mSelectedMedia = mCastManager.getRemoteMediaInformation();
+ updateClosedCaptionState();
+ updateMetadata();
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update the metadata due to network issues", e);
+ }
+ }
+
+ @Override
+ public void onFailed(int resourceId, int statusCode) {
+ LOGD(TAG, "onFailed(): " + getString(resourceId) + ", status code: " + statusCode);
+ if (statusCode == RemoteMediaPlayer.STATUS_FAILED
+ || statusCode == RemoteMediaPlayer.STATUS_TIMED_OUT) {
+ Utils.showToast(getActivity(), resourceId);
+ mCastController.closeActivity();
+ }
+ }
+
+ @Override
+ public void onRemoteMediaPlayerStatusUpdated() {
+ updatePlayerStatus();
+ }
+
+ @Override
+ public void onMediaQueueUpdated(List queueItems, MediaQueueItem item,
+ int repeatMode, boolean shuffle) {
+
+ int size = 0;
+ int position = 0;
+ if (queueItems != null) {
+ size = queueItems.size();
+ position = queueItems.indexOf(item);
+ }
+ mCastController.onQueueItemsUpdated(size, position);
+ }
+
+ @Override
+ public void onConnectionSuspended(int cause) {
+ mCastController.updateControllersStatus(false);
+ }
+
+ @Override
+ public void onConnectivityRecovered() {
+ mCastController.updateControllersStatus(true);
+ }
+
+ }
+
+ private class UpdateSeekbarTask extends TimerTask {
+
+ @Override
+ public void run() {
+ mHandler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ int currentPos;
+ if (mPlaybackState == MediaStatus.PLAYER_STATE_BUFFERING) {
+ return;
+ }
+ if (!mCastManager.isConnected()) {
+ return;
+ }
+ try {
+ int duration = (int) mCastManager.getMediaDuration();
+ if (duration > 0) {
+ try {
+ currentPos = (int) mCastManager.getCurrentMediaPosition();
+ mCastController.updateSeekbar(currentPos, duration);
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to get current media position", e);
+ }
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update the progress bar due to network issues", e);
+ }
+
+ }
+ });
+ }
+ }
+
+ /**
+ * Loads the media on the cast device.
+ *
+ * @param mediaInfo The media to be loaded
+ * @param shouldStartPlayback If {@code true}, playback starts after load automatically
+ * @param startPoint The position to start the play back
+ * @param customData An optional custom data to be sent along the load api; it can be
+ * {@code null}
+ */
+ private void onReady(MediaInfo mediaInfo, boolean shouldStartPlayback, int startPoint,
+ JSONObject customData) {
+ mSelectedMedia = mediaInfo;
+ updateClosedCaptionState();
+ try {
+ mCastController.setStreamType(mSelectedMedia.getStreamType());
+ if (shouldStartPlayback) {
+ // need to start remote playback
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ mCastManager.loadMedia(mSelectedMedia, true, startPoint, customData);
+ } else {
+ // we don't change the status of remote playback
+ if (mCastManager.isRemoteMediaPlaying()) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_PLAYING;
+ } else {
+ mPlaybackState = MediaStatus.PLAYER_STATE_PAUSED;
+ }
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to get playback and media information", e);
+ mCastController.closeActivity();
+ }
+ MediaQueue mediaQueue = mCastManager.getMediaQueue();
+ int size = 0;
+ int position = 0;
+ if (mediaQueue != null) {
+ size = mediaQueue.getCount();
+ position = mediaQueue.getCurrentItemPosition();
+ }
+ mCastController.onQueueItemsUpdated(size, position);
+ updateMetadata();
+ restartTrickplayTimer();
+ }
+
+ private void updateClosedCaptionState() {
+ int state = VideoCastController.CC_HIDDEN;
+ if (mCastManager.isFeatureEnabled(VideoCastManager.FEATURE_CAPTIONS_PREFERENCE)
+ && mSelectedMedia != null
+ && mCastManager.getTracksPreferenceManager().isCaptionEnabled()) {
+ List tracks = mSelectedMedia.getMediaTracks();
+ state = hasAudioOrTextTrack(tracks) ? VideoCastController.CC_ENABLED
+ : VideoCastController.CC_DISABLED;
+ }
+ mCastController.setClosedCaptionState(state);
+ }
+
+ private boolean hasAudioOrTextTrack(List tracks) {
+ if (tracks == null || tracks.isEmpty()) {
+ return false;
+ }
+ for (MediaTrack track : tracks) {
+ if (track.getType() == MediaTrack.TYPE_AUDIO
+ || track.getType() == MediaTrack.TYPE_TEXT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stopTrickplayTimer() {
+ LOGD(TAG, "Stopped TrickPlay Timer");
+ if (mSeekbarTimer != null) {
+ mSeekbarTimer.cancel();
+ }
+ }
+
+ private void restartTrickplayTimer() {
+ stopTrickplayTimer();
+ mSeekbarTimer = new Timer();
+ mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), 100, 1000);
+ LOGD(TAG, "Restarted TrickPlay Timer");
+ }
+
+ private void updateOverallState() {
+ MediaAuthService authService;
+ switch (mOverallState) {
+ case AUTHORIZING:
+ authService = mCastManager.getMediaAuthService();
+ if (authService != null) {
+ mCastController.setSubTitle(authService.getPendingMessage() != null
+ ? authService.getPendingMessage() : "");
+ mCastController.showLoading(true);
+ }
+ break;
+ case PLAYBACK:
+ // nothing yet, may be needed in future
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void updateMetadata() {
+ Uri imageUrl = null;
+ if (mSelectedMedia == null) {
+ if (mMediaAuthService != null) {
+ imageUrl = Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1);
+ }
+ } else {
+ imageUrl = Utils.getImageUri(mSelectedMedia, 1);
+ }
+ showImage(imageUrl);
+ if (mSelectedMedia == null) {
+ return;
+ }
+ MediaMetadata mm = mSelectedMedia.getMetadata();
+ mCastController.setTitle(mm.getString(MediaMetadata.KEY_TITLE) != null
+ ? mm.getString(MediaMetadata.KEY_TITLE) : "");
+ boolean isLive = mSelectedMedia.getStreamType() == MediaInfo.STREAM_TYPE_LIVE;
+ mCastController.adjustControllersForLiveStream(isLive);
+ }
+
+ private void updatePlayerStatus() {
+ int mediaStatus = mCastManager.getPlaybackStatus();
+ mMediaStatus = mCastManager.getMediaStatus();
+ LOGD(TAG, "updatePlayerStatus(), state: " + mediaStatus);
+ if (mSelectedMedia == null) {
+ return;
+ }
+ mCastController.setStreamType(mSelectedMedia.getStreamType());
+ if (mediaStatus == MediaStatus.PLAYER_STATE_BUFFERING) {
+ mCastController.setSubTitle(getString(R.string.ccl_loading));
+ } else {
+ mCastController.setSubTitle(getString(R.string.ccl_casting_to_device,
+ mCastManager.getDeviceName()));
+ }
+ switch (mediaStatus) {
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ mIsFresh = false;
+ if (mPlaybackState != MediaStatus.PLAYER_STATE_PLAYING) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_PLAYING;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ mIsFresh = false;
+ if (mPlaybackState != MediaStatus.PLAYER_STATE_PAUSED) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_PAUSED;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ mIsFresh = false;
+ if (mPlaybackState != MediaStatus.PLAYER_STATE_BUFFERING) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ LOGD(TAG, "Idle Reason: " + (mCastManager.getIdleReason()));
+ switch (mCastManager.getIdleReason()) {
+ case MediaStatus.IDLE_REASON_FINISHED:
+ if (!mIsFresh && mMediaStatus.getLoadingItemId()
+ == MediaQueueItem.INVALID_ITEM_ID) {
+ mCastController.closeActivity();
+ }
+ break;
+ case MediaStatus.IDLE_REASON_CANCELED:
+ try {
+ if (mCastManager.isRemoteStreamLive()) {
+ if (mPlaybackState != MediaStatus.PLAYER_STATE_IDLE) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_IDLE;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+ } else {
+ mCastController.closeActivity();
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGD(TAG, "Failed to determine if stream is live", e);
+ }
+ break;
+ case MediaStatus.IDLE_REASON_INTERRUPTED:
+ mPlaybackState = MediaStatus.PLAYER_STATE_IDLE;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ LOGD(TAG, "onDestroy()");
+ stopTrickplayTimer();
+ cleanup();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ try {
+ if (mCastManager.isRemoteMediaPaused() || mCastManager.isRemoteMediaPlaying()) {
+ if (mCastManager.getRemoteMediaInformation() != null && mSelectedMedia
+ .getContentId().equals(
+ mCastManager.getRemoteMediaInformation().getContentId())) {
+ mIsFresh = false;
+ }
+ }
+ if (!mCastManager.isConnecting()) {
+ boolean shouldFinish = !mCastManager.isConnected()
+ || (mCastManager.getPlaybackStatus() == MediaStatus.PLAYER_STATE_IDLE
+ && mCastManager.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED);
+ if (shouldFinish && !mIsFresh) {
+ mCastController.closeActivity();
+ return;
+ }
+ }
+ mMediaStatus = mCastManager.getMediaStatus();
+ mCastManager.addVideoCastConsumer(mCastConsumer);
+ if (!mIsFresh) {
+ updatePlayerStatus();
+ // updating metadata in case another client has changed it and we are resuming the
+ // activity
+ mSelectedMedia = mCastManager.getRemoteMediaInformation();
+ updateClosedCaptionState();
+ updateMetadata();
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to get media information or status of media playback", e);
+ } finally {
+ mCastManager.incrementUiCounter();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mCastManager.removeVideoCastConsumer(mCastConsumer);
+ mCastManager.decrementUiCounter();
+ mIsFresh = false;
+ super.onPause();
+ }
+
+ /**
+ * Call this static method to create an instance of this fragment.
+ */
+ public static VideoCastControllerFragment newInstance(Bundle extras) {
+ VideoCastControllerFragment f = new VideoCastControllerFragment();
+ Bundle b = new Bundle();
+ b.putBundle(EXTRAS, extras);
+ f.setArguments(b);
+ return f;
+ }
+
+ /*
+ * Gets the image at the given url and populates the image view with that. It tries to cache the
+ * image to avoid unnecessary network calls.
+ */
+ private void showImage(final Uri uri) {
+ if (mImageAsyncTask != null) {
+ mImageAsyncTask.cancel(true);
+ }
+ if (uri == null) {
+ mCastController.setImage(BitmapFactory.decodeResource(getActivity().getResources(),
+ R.drawable.album_art_placeholder_large));
+ return;
+ }
+ if (mUrlAndBitmap != null && mUrlAndBitmap.isMatch(uri)) {
+ // we can reuse mBitmap
+ mCastController.setImage(mUrlAndBitmap.mBitmap);
+ return;
+ }
+ mUrlAndBitmap = null;
+ if (mImageAsyncTask != null) {
+ mImageAsyncTask.cancel(true);
+ }
+ mImageAsyncTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ mUrlAndBitmap = new UrlAndBitmap();
+ mUrlAndBitmap.mBitmap = bitmap;
+ mUrlAndBitmap.mUrl = uri;
+ if (!isCancelled()) {
+ mCastController.setImage(bitmap);
+ }
+ }
+ if (this == mImageAsyncTask) {
+ mImageAsyncTask = null;
+ }
+ }
+ };
+ mImageAsyncTask.execute(uri);
+ }
+
+ /**
+ * A modal dialog with an OK button, where upon clicking on it, will finish the activity. We
+ * use a DialogFragment so during configuration changes, system manages the dialog for us.
+ */
+ public static class ErrorDialogFragment extends DialogFragment {
+
+ private VideoCastController mController;
+ private static final String MESSAGE = "message";
+
+ public static ErrorDialogFragment newInstance(String message) {
+ ErrorDialogFragment frag = new ErrorDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(MESSAGE, message);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ mController = (VideoCastController) activity;
+ super.onAttach(activity);
+ setCancelable(false);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ String message = getArguments().getString(MESSAGE);
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.ccl_error)
+ .setMessage(message)
+ .setPositiveButton(R.string.ccl_ok, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ sDialogCanceled = true;
+ mController.closeActivity();
+ }
+ })
+ .create();
+ }
+ }
+
+ /*
+ * Shows an error dialog
+ */
+ private void showErrorDialog(String message) {
+ ErrorDialogFragment.newInstance(message).show(getFragmentManager(), "dlg");
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mImageAsyncTask != null) {
+ mImageAsyncTask.cancel(true);
+ mImageAsyncTask = null;
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ try {
+ if (mPlaybackState == MediaStatus.PLAYER_STATE_PLAYING) {
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ mCastController.setPlaybackStatus(mPlaybackState);
+ mCastManager.play(seekBar.getProgress());
+ } else if (mPlaybackState == MediaStatus.PLAYER_STATE_PAUSED) {
+ mCastManager.seek(seekBar.getProgress());
+ }
+ restartTrickplayTimer();
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to complete seek", e);
+ mCastController.closeActivity();
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ stopTrickplayTimer();
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ }
+
+ @Override
+ public void onPlayPauseClicked(View v) throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException {
+ LOGD(TAG, "isConnected returning: " + mCastManager.isConnected());
+ togglePlayback();
+ }
+
+ private void togglePlayback() throws CastException, TransientNetworkDisconnectionException,
+ NoConnectionException {
+ switch (mPlaybackState) {
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ mCastManager.play();
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ restartTrickplayTimer();
+ break;
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ mCastManager.pause();
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ if ((mSelectedMedia.getStreamType() == MediaInfo.STREAM_TYPE_LIVE)
+ && (mCastManager.getIdleReason() == MediaStatus.IDLE_REASON_CANCELED)) {
+ mCastManager.play();
+ } else {
+ mCastManager.loadMedia(mSelectedMedia, true, 0);
+ }
+ mPlaybackState = MediaStatus.PLAYER_STATE_BUFFERING;
+ restartTrickplayTimer();
+ break;
+ default:
+ break;
+ }
+ mCastController.setPlaybackStatus(mPlaybackState);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateOverallState();
+ if (mSelectedMedia == null) {
+ if (mMediaAuthService != null) {
+ showImage(Utils.getImageUri(mMediaAuthService.getMediaInfo(), 1));
+ }
+ } else {
+ updateMetadata();
+ updatePlayerStatus();
+ mCastController.updateControllersStatus(mCastManager.isConnected());
+
+ }
+ }
+
+ @Override
+ public void onAuthResult(MediaAuthStatus status, final MediaInfo info, final String message,
+ final int startPoint, final JSONObject customData) {
+ if (status == MediaAuthStatus.AUTHORIZED && mAuthSuccess) {
+ // successful authorization
+ mMediaAuthService = null;
+ if (mMediaAuthTimer != null) {
+ mMediaAuthTimer.cancel();
+ }
+ mSelectedMedia = info;
+ updateClosedCaptionState();
+ mHandler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ mOverallState = OverallState.PLAYBACK;
+ onReady(info, true, startPoint, customData);
+ }
+ });
+ } else {
+ if (mMediaAuthTimer != null) {
+ mMediaAuthTimer.cancel();
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mOverallState = OverallState.UNKNOWN;
+ showErrorDialog(message);
+ }
+ });
+
+ }
+ }
+
+ @Override
+ public void onAuthFailure(final String failureMessage) {
+ if (mMediaAuthTimer != null) {
+ mMediaAuthTimer.cancel();
+ }
+ mHandler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ mOverallState = OverallState.UNKNOWN;
+ showErrorDialog(failureMessage);
+ }
+ });
+
+ }
+
+ @Override
+ public void onTracksSelected(List tracks) {
+ long[] tracksArray;
+ if (tracks.isEmpty()) {
+ tracksArray = new long[]{};
+ } else {
+ tracksArray = new long[tracks.size()];
+ for (int i = 0; i < tracks.size(); i++) {
+ tracksArray[i] = tracks.get(i).getId();
+ }
+ }
+ mCastManager.setActiveTrackIds(tracksArray);
+ if (tracks.size() > 0) {
+ mCastManager.setTextTrackStyle(mCastManager.getTracksPreferenceManager()
+ .getTextTrackStyle());
+ }
+ }
+
+ /*
+ * A simple class that holds a URL and a bitmap, mainly used to cache the fetched image
+ */
+ private class UrlAndBitmap {
+
+ private Bitmap mBitmap;
+ private Uri mUrl;
+
+ private boolean isMatch(Uri url) {
+ return url != null && mBitmap != null && url.equals(mUrl);
+ }
+ }
+
+ /*
+ * Cleanup of threads and timers and bitmap and ...
+ */
+ private void cleanup() {
+ MediaAuthService authService = mCastManager.getMediaAuthService();
+ if (mMediaAuthTimer != null) {
+ mMediaAuthTimer.cancel();
+ }
+ if (mAuthThread != null) {
+ mAuthThread = null;
+ }
+ if (mCastManager.getMediaAuthService() != null) {
+ authService.setMediaAuthListener(null);
+ mCastManager.removeMediaAuthService();
+ }
+ if (mCastManager != null) {
+ mCastManager.removeVideoCastConsumer(mCastConsumer);
+ }
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ if (mUrlAndBitmap != null) {
+ mUrlAndBitmap.mBitmap = null;
+ }
+ if (!sDialogCanceled && mMediaAuthService != null) {
+ mMediaAuthService.abortAuthorization(MediaAuthStatus.CANCELED_BY_USER);
+ }
+
+ mCastManager.removeTracksSelectedListener(this);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void setImmersive() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ return;
+ }
+ int newUiOptions = getActivity().getWindow().getDecorView().getSystemUiVisibility();
+
+ // Navigation bar hiding: Backwards compatible to ICS.
+ if (Build.VERSION.SDK_INT >= 14) {
+ newUiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ }
+
+ // Status bar hiding: Backwards compatible to Jellybean
+ if (Build.VERSION.SDK_INT >= 16) {
+ newUiOptions ^= View.SYSTEM_UI_FLAG_FULLSCREEN;
+ }
+
+ if (Build.VERSION.SDK_INT >= 18) {
+ newUiOptions ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ }
+
+ getActivity().getWindow().getDecorView().setSystemUiVisibility(newUiOptions);
+ }
+
+ @Override
+ public void onSkipNextClicked(View v)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ mCastController.showLoading(true);
+ mCastManager.queueNext(null);
+ }
+
+ @Override
+ public void onSkipPreviousClicked(View v)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ mCastController.showLoading(true);
+ mCastManager.queuePrev(null);
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/reconnection/ReconnectionService.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/reconnection/ReconnectionService.java
new file mode 100644
index 000000000..f56abe067
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/reconnection/ReconnectionService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.reconnection;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.os.IBinder;
+import android.os.SystemClock;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A service to run in the background when the playback of a media starts, to help with reconnection
+ * if needed. Due to various reasons, connectivity to the cast device can be lost; for example wifi
+ * radio may turn off when device goes to sleep or user may step outside of the wifi range, etc.
+ * This service helps with recovering the connectivity when circumstances are right, for example
+ * when user steps back within the wifi range, etc. In order to avoid ending up with a background
+ * service that lingers around longer than it is needed, this implementation uses certain heuristics
+ * to stop itself when needed.
+ */
+public class ReconnectionService extends Service {
+
+ private static final String TAG = LogUtils.makeLogTag(ReconnectionService.class);
+ // the tolerance for considering a time value (in millis) to be zero
+ private static final long EPSILON_MS = 500;
+ private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15;
+ private BroadcastReceiver mScreenOnOffBroadcastReceiver;
+ private VideoCastManager mCastManager;
+ private BroadcastReceiver mWifiBroadcastReceiver;
+ private boolean mWifiConnectivity = true;
+ private Timer mEndTimer;
+ private TimerTask mEndTimerTask;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LOGD(TAG, "onStartCommand() is called");
+ setUpEndTimer();
+ return Service.START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ LOGD(TAG, "onCreate() is called");
+ mCastManager = VideoCastManager.getInstance();
+ if (!mCastManager.isConnected() && !mCastManager.isConnecting()) {
+ mCastManager.reconnectSessionIfPossible();
+ }
+
+ // register a broadcast receiver to be notified when screen goes on or off
+ IntentFilter screenOnOffIntentFilter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ screenOnOffIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ mScreenOnOffBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ LOGD(TAG, "ScreenOnOffBroadcastReceiver: onReceive(): " + intent.getAction());
+ long timeLeft = getMediaRemainingTime();
+ if (timeLeft < EPSILON_MS) {
+ handleTermination();
+ }
+ }
+ };
+ registerReceiver(mScreenOnOffBroadcastReceiver, screenOnOffIntentFilter);
+
+ // register a wifi receiver that would be notified when the network state changes
+ IntentFilter networkIntentFilter = new IntentFilter();
+ networkIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mWifiBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ boolean connected = info.isConnected();
+ String networkSsid = connected ? Utils.getWifiSsid(context) : null;
+ ReconnectionService.this.onWifiConnectivityChanged(connected, networkSsid);
+ }
+ }
+ };
+ registerReceiver(mWifiBroadcastReceiver, networkIntentFilter);
+
+ super.onCreate();
+ }
+
+ /**
+ * Since framework calls this method twice when a change happens, we are guarding against that
+ * by caching the state the first time and avoiding the second call if it is the same status.
+ */
+ public void onWifiConnectivityChanged(boolean connected, final String networkSsid) {
+ LOGD(TAG, "WIFI connectivity changed to " + (connected ? "enabled" : "disabled"));
+ if (connected && !mWifiConnectivity) {
+ mWifiConnectivity = true;
+ if (mCastManager.isFeatureEnabled(BaseCastManager.FEATURE_WIFI_RECONNECT)) {
+ mCastManager.startCastDiscovery();
+ mCastManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, networkSsid);
+ }
+
+ } else {
+ mWifiConnectivity = connected;
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ LOGD(TAG, "onDestroy()");
+ if (mScreenOnOffBroadcastReceiver != null) {
+ unregisterReceiver(mScreenOnOffBroadcastReceiver);
+ mScreenOnOffBroadcastReceiver = null;
+ }
+
+ if (mWifiBroadcastReceiver != null) {
+ unregisterReceiver(mWifiBroadcastReceiver);
+ mWifiBroadcastReceiver = null;
+ }
+
+ clearEndTimer();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void setUpEndTimer() {
+ LOGD(TAG, "setUpEndTimer(): setting up a timer for the end of current media");
+ long timeLeft = getMediaRemainingTime();
+ if (timeLeft <= 0) {
+ stopSelf();
+ return;
+ }
+ clearEndTimer();
+ mEndTimer = new Timer();
+ mEndTimerTask = new TimerTask() {
+ @Override
+ public void run() {
+ LOGD(TAG, "setUpEndTimer(): stopping ReconnectionService since reached the end of"
+ + " allotted time");
+ handleTermination();
+ }
+ };
+ mEndTimer.schedule(mEndTimerTask, timeLeft);
+ }
+
+ private void clearEndTimer() {
+ if (mEndTimerTask != null) {
+ mEndTimerTask.cancel();
+ mEndTimerTask = null;
+ }
+
+ if (mEndTimer != null) {
+ mEndTimer.cancel();
+ mEndTimer = null;
+ }
+ }
+
+ private long getMediaRemainingTime() {
+ long endTime = mCastManager.getPreferenceAccessor().getLongFromPreference(
+ BaseCastManager.PREFS_KEY_MEDIA_END, 0);
+ return endTime - SystemClock.elapsedRealtime();
+ }
+
+ private void handleTermination() {
+ if (!mCastManager.isConnected()) {
+ mCastManager.clearMediaSession();
+ mCastManager.clearPersistedConnectionInfo(BaseCastManager.CLEAR_ALL);
+ stopSelf();
+ } else {
+ // since we are connected and our timer has gone off, lets update the time remaining
+ // on the media (since media may have been paused) and reset teh time left
+ long timeLeft = 0;
+ try {
+ timeLeft = mCastManager.isRemoteStreamLive() ? 0
+ : mCastManager.getMediaTimeRemaining();
+
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to calculate the time left for media due to lack of connectivity",
+ e);
+ }
+ if (timeLeft < EPSILON_MS) {
+ // no time left
+ stopSelf();
+ } else {
+ // lets reset the counter
+ mCastManager.getPreferenceAccessor().saveLongToPreference(
+ BaseCastManager.PREFS_KEY_MEDIA_END,
+ timeLeft + SystemClock.elapsedRealtime());
+ LOGD(TAG, "handleTermination(): resetting the timer");
+ setUpEndTimer();
+ }
+
+ }
+ }
+}
+
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/CaptionsPreferenceActivity.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/CaptionsPreferenceActivity.java
new file mode 100644
index 000000000..d1ca5758d
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/CaptionsPreferenceActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.tracks;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.provider.Settings;
+
+/**
+ * An Activity to show the Captions Preferences for Android versions prior to KitKat
+ */
+public class CaptionsPreferenceActivity extends PreferenceActivity {
+
+ private static final String TAG = LogUtils.makeLogTag(CaptionsPreferenceActivity.class);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ VideoCastManager castManager = VideoCastManager.getInstance();
+ if (!castManager.isFeatureEnabled(VideoCastManager.FEATURE_CAPTIONS_PREFERENCE)) {
+ LOGE(TAG, "Did you forget to enable FEATURE_CAPTIONS_PREFERENCE when you initialized"
+ + " the VideoCastManage?");
+ finish();
+ return;
+ }
+ if (Utils.IS_KITKAT_OR_ABOVE) {
+ startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
+ finish();
+ return;
+ }
+ addPreferencesFromResource(R.xml.caption_preference);
+ castManager.getTracksPreferenceManager().setUpPreferences(getPreferenceScreen());
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/OnTracksSelectedListener.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/OnTracksSelectedListener.java
new file mode 100644
index 000000000..eb29320cb
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/OnTracksSelectedListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.tracks;
+
+import com.google.android.gms.cast.MediaTrack;
+
+import java.util.List;
+
+/**
+ * An interface to listen to changes to the active tracks for a media.
+ */
+public interface OnTracksSelectedListener {
+
+ /**
+ * Called to inform the listeners of the new set of active tracks.
+ *
+ * @param tracks A Non-null
list of MediaTracks.
+ */
+ void onTracksSelected(List tracks);
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/TracksPreferenceManager.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/TracksPreferenceManager.java
new file mode 100644
index 000000000..faee35f23
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/TracksPreferenceManager.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.tracks;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+
+import com.google.android.gms.cast.TextTrackStyle;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.PreferenceAccessor;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.view.accessibility.CaptioningManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class manages preference settings for captions for Android versions prior to KitKat and
+ * provides a number of methods that would work across all supported versions of Android.
+ */
+public class TracksPreferenceManager implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static final String TAG = LogUtils.makeLogTag(TracksPreferenceManager.class);
+
+ private final Context mContext;
+ private final SharedPreferences mSharedPreferences;
+ private final PreferenceAccessor mPreferenceAccessor;
+
+ private static final String FONT_FAMILY_SANS_SERIF = "FONT_FAMILY_SANS_SERIF";
+ private static final String EDGE_TYPE_DEFAULT = "EDGE_TYPE_NONE";
+ private static final Map OPACITY_MAPPING = new HashMap<>();
+ private static final Map FONT_FAMILY_MAPPING = new HashMap<>();
+ private static final Map EDGE_TYPE_MAPPING = new HashMap<>();
+
+ private ListPreference mCaptionFontScaleListPreference;
+ private ListPreference mCaptionFontFamilyListPreference;
+ private ListPreference mCaptionTextColorListPreference;
+ private ListPreference mCaptionTextOpacityListPreference;
+ private ListPreference mCaptionEdgeTypeListPreference;
+ private ListPreference mCaptionBackgroundColorListPreference;
+ private ListPreference mCaptionBackgroundOpacityListPreference;
+
+ private CheckBoxPreference mCaptionAvailability;
+ private boolean isInitialized = false;
+
+ static {
+ OPACITY_MAPPING.put("FF", "100");
+ OPACITY_MAPPING.put("BF", "75");
+ OPACITY_MAPPING.put("80", "50");
+ OPACITY_MAPPING.put("3F", "25");
+ }
+
+ static {
+ FONT_FAMILY_MAPPING.put("FONT_FAMILY_SANS_SERIF", TextTrackStyle.FONT_FAMILY_SANS_SERIF);
+ FONT_FAMILY_MAPPING.put("FONT_FAMILY_SERIF", TextTrackStyle.FONT_FAMILY_SERIF);
+ FONT_FAMILY_MAPPING.put("FONT_FAMILY_MONOSPACED_SANS_SERIF",
+ TextTrackStyle.FONT_FAMILY_MONOSPACED_SANS_SERIF);
+ }
+
+ static {
+ EDGE_TYPE_MAPPING.put("EDGE_TYPE_NONE", TextTrackStyle.EDGE_TYPE_NONE);
+ EDGE_TYPE_MAPPING.put("EDGE_TYPE_OUTLINE", TextTrackStyle.EDGE_TYPE_OUTLINE);
+ EDGE_TYPE_MAPPING.put("EDGE_TYPE_DROP_SHADOW", TextTrackStyle.EDGE_TYPE_DROP_SHADOW);
+ }
+
+ public TracksPreferenceManager(Context context) {
+ mContext = context;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ mPreferenceAccessor = VideoCastManager.getInstance().getPreferenceAccessor();
+ }
+
+ public TextTrackStyle getTextTrackStyle() {
+ final TextTrackStyle textTrackStyle = TextTrackStyle.fromSystemSettings(mContext);
+ if (Utils.IS_KITKAT_OR_ABOVE) {
+ return textTrackStyle;
+ } else {
+ // we need to populate all the fields ourselves
+ textTrackStyle.setFontGenericFamily(FONT_FAMILY_MAPPING.get(getFontFamily()));
+ textTrackStyle.setBackgroundColor(Color.parseColor(getBackgroundColor()));
+ textTrackStyle.setEdgeType(EDGE_TYPE_MAPPING.get(getEdgeType()));
+ textTrackStyle.setFontScale(getFontScale());
+ boolean isBold = Typeface.DEFAULT.isBold();
+ boolean isItalic = Typeface.DEFAULT.isItalic();
+ int fontStyle = TextTrackStyle.FONT_STYLE_NORMAL;
+ if (isBold && isItalic) {
+ fontStyle = TextTrackStyle.FONT_STYLE_BOLD_ITALIC;
+ } else if (!isBold && !isItalic) {
+ fontStyle = TextTrackStyle.FONT_STYLE_NORMAL;
+ } else if (isBold) {
+ fontStyle = TextTrackStyle.FONT_STYLE_BOLD;
+ }
+ textTrackStyle.setFontStyle(fontStyle);
+ textTrackStyle.setForegroundColor(
+ combineColorAndOpacity(getTextColor(), getTextOpacity()));
+ LOGD(TAG, "Edge is: " + getEdgeType());
+ textTrackStyle.setBackgroundColor(combineColorAndOpacity(getBackgroundColor(),
+ getBackgroundOpacity())
+ );
+ }
+
+ return textTrackStyle;
+ }
+
+ @SuppressLint("NewApi")
+ public boolean isCaptionEnabled() {
+ if (Utils.IS_KITKAT_OR_ABOVE) {
+ CaptioningManager captioningManager =
+ (CaptioningManager) mContext.getSystemService(Context.CAPTIONING_SERVICE);
+ return captioningManager.isEnabled();
+ } else {
+ return mPreferenceAccessor.getBooleanFromPreference(
+ mContext.getString(R.string.ccl_key_caption_enabled), false);
+ }
+ }
+
+ public void setFontFamily(String fontFamily) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_font_family), fontFamily);
+ }
+
+ public String getFontFamily() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_font_family), FONT_FAMILY_SANS_SERIF);
+ }
+
+ public void setFontScale(String value) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_font_scale), value);
+ }
+
+ public float getFontScale() {
+ String scaleStr = mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_font_scale),
+ String.valueOf(TextTrackStyle.DEFAULT_FONT_SCALE));
+ return Float.parseFloat(scaleStr);
+ }
+
+ public void setTextColor(String textColor) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_text_color), textColor);
+ }
+
+ public String getTextColor() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_text_color),
+ mContext.getString(R.string.ccl_prefs_caption_text_color_value_default));
+ }
+
+ public void setTextOpacity(String textColor) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_text_opacity), textColor);
+ }
+
+ public String getTextOpacity() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_text_opacity),
+ mContext.getString(R.string.ccl_prefs_caption_text_opacity_value_default));
+ }
+
+ public void setEdgeType(String textColor) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_edge_type), textColor);
+ }
+
+ public String getEdgeType() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_edge_type), EDGE_TYPE_DEFAULT);
+ }
+
+ public void setBackgroundColor(Context mContext, String textColor) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_background_color), textColor);
+ }
+
+ public String getBackgroundColor() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_background_color),
+ mContext.getString(R.string.ccl_prefs_caption_background_color_value_default));
+ }
+
+ public void setBackgroundOpacity(String textColor) {
+ mPreferenceAccessor.saveStringToPreference(
+ mContext.getString(R.string.ccl_key_caption_background_opacity), textColor);
+ }
+
+ public String getBackgroundOpacity() {
+ return mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_background_opacity),
+ mContext.getString(R.string.ccl_prefs_caption_background_opacity_value_default));
+ }
+
+ public void setUpPreferences(PreferenceScreen screen) {
+ mCaptionAvailability = (CheckBoxPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_enabled));
+
+ mCaptionFontScaleListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_font_scale));
+
+ mCaptionFontFamilyListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_font_family));
+
+ mCaptionTextColorListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_text_color));
+
+ mCaptionTextOpacityListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_text_opacity));
+
+ mCaptionEdgeTypeListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_edge_type));
+
+ mCaptionBackgroundColorListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_background_color));
+
+ mCaptionBackgroundOpacityListPreference = (ListPreference) screen.findPreference(
+ mContext.getString(R.string.ccl_key_caption_background_opacity));
+ isInitialized = true;
+
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_enabled), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_font_family), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_font_scale), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_text_color), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_text_opacity), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_edge_type), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_background_color), false);
+ onSharedPreferenceChanged(mSharedPreferences,
+ mContext.getString(R.string.ccl_key_caption_background_opacity), false);
+ }
+
+ private void setCaptionAvailability(boolean status) {
+ mCaptionFontScaleListPreference.setEnabled(status);
+ mCaptionFontFamilyListPreference.setEnabled(status);
+ mCaptionTextColorListPreference.setEnabled(status);
+ mCaptionTextOpacityListPreference.setEnabled(status);
+ mCaptionEdgeTypeListPreference.setEnabled(status);
+ mCaptionBackgroundColorListPreference.setEnabled(status);
+ mCaptionBackgroundOpacityListPreference.setEnabled(status);
+ }
+
+ /**
+ * Returns the label of the selected item in a list preference, to be used for the summary of
+ * that preference item
+ */
+ private String getCaptionSummaryForList(SharedPreferences sharedPreferences, int keyResourceId,
+ int defaultResourceId, int namesResourceId, int valuesResourceId) {
+ Resources resources = mContext.getResources();
+ String value = sharedPreferences.getString(resources.getString(keyResourceId),
+ resources.getString(defaultResourceId));
+ String[] labels = resources.getStringArray(namesResourceId);
+ String[] values = resources.getStringArray(valuesResourceId);
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) {
+ return labels[i];
+ }
+ }
+ return "";
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ onSharedPreferenceChanged(sharedPreferences, key, true);
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key, boolean broadcast) {
+ if (!isInitialized) {
+ return;
+ }
+ if (mContext.getString(R.string.ccl_key_caption_enabled).equals(key)) {
+ mCaptionAvailability.setSummary(
+ mCaptionAvailability.isChecked() ? R.string.ccl_prefs_caption_enabled
+ : R.string.ccl_prefs_caption_disabled
+ );
+ setCaptionAvailability(mCaptionAvailability.isChecked());
+ if (broadcast) {
+ VideoCastManager.getInstance()
+ .onTextTrackEnabledChanged(mCaptionAvailability.isChecked());
+ }
+ return;
+ }
+
+ if (mContext.getString(R.string.ccl_key_caption_font_scale).equals(key)) {
+ mCaptionFontScaleListPreference
+ .setSummary(
+ getCaptionSummaryForList(sharedPreferences,
+ R.string.ccl_key_caption_font_scale,
+ R.string.ccl_prefs_caption_font_scale_value_default,
+ R.array.ccl_prefs_caption_font_scale_names,
+ R.array.ccl_prefs_caption_font_scale_values)
+ );
+ } else if (mContext.getString(R.string.ccl_key_caption_font_family).equals(key)) {
+ mCaptionFontFamilyListPreference
+ .setSummary(
+ getCaptionSummaryForList(sharedPreferences,
+ R.string.ccl_key_caption_font_family,
+ R.string.ccl_prefs_caption_font_family_value_default,
+ R.array.ccl_prefs_caption_font_family_names,
+ R.array.ccl_prefs_caption_font_family_values)
+ );
+ } else if (mContext.getString(R.string.ccl_key_caption_text_color).equals(key)) {
+ mCaptionTextColorListPreference
+ .setSummary(
+ getCaptionSummaryForList(sharedPreferences,
+ R.string.ccl_key_caption_text_color,
+ R.string.ccl_prefs_caption_text_color_value_default,
+ R.array.ccl_prefs_caption_color_names,
+ R.array.ccl_prefs_caption_color_values)
+ );
+ } else if (mContext.getString(R.string.ccl_key_caption_text_opacity).equals(key)) {
+ String opacity = mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_text_opacity),
+ mContext.getString(R.string.ccl_prefs_caption_text_opacity_value_default));
+ mCaptionTextOpacityListPreference
+ .setSummary(OPACITY_MAPPING.get(opacity) + "%%");
+ } else if (mContext.getString(R.string.ccl_key_caption_edge_type).equals(key)) {
+ mCaptionEdgeTypeListPreference
+ .setSummary(
+ getCaptionSummaryForList(sharedPreferences,
+ R.string.ccl_key_caption_edge_type,
+ R.string.ccl_prefs_caption_edge_type_value_default,
+ R.array.ccl_prefs_caption_edge_type_names,
+ R.array.ccl_prefs_caption_edge_type_values)
+ );
+ } else if (mContext.getString(R.string.ccl_key_caption_background_color).equals(key)) {
+ mCaptionBackgroundColorListPreference
+ .setSummary(getCaptionSummaryForList(sharedPreferences,
+ R.string.ccl_key_caption_background_color,
+ R.string.ccl_prefs_caption_background_color_value_default,
+ R.array.ccl_prefs_caption_color_names,
+ R.array.ccl_prefs_caption_color_values));
+ } else if (mContext.getString(R.string.ccl_key_caption_background_opacity).equals(key)) {
+ String opacity = mPreferenceAccessor.getStringFromPreference(
+ mContext.getString(R.string.ccl_key_caption_background_opacity),
+ mContext.getString(R.string.ccl_prefs_caption_background_opacity_value_default));
+ mCaptionBackgroundOpacityListPreference
+ .setSummary(OPACITY_MAPPING.get(opacity) + "%%");
+ }
+ if (broadcast) {
+ VideoCastManager.getInstance().onTextTrackStyleChanged(getTextTrackStyle());
+ }
+
+ }
+
+ private static int combineColorAndOpacity(String color, String opacity) {
+ color = color.replace("#", "");
+ return Color.parseColor("#" + opacity + color);
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksChooserDialog.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksChooserDialog.java
new file mode 100644
index 000000000..ac3ca8054
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksChooserDialog.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.tracks.ui;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaTrack;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.TabHost;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A dialog to show the available tracks (Text and Audio) for user to select.
+ */
+public class TracksChooserDialog extends DialogFragment {
+
+ private VideoCastManager mCastManager;
+ private long[] mActiveTracks = null;
+ private MediaInfo mMediaInfo;
+ private TracksListAdapter mTextAdapter;
+ private TracksListAdapter mAudioVideoAdapter;
+ private List mTextTracks = new ArrayList<>();
+ private List mAudioTracks = new ArrayList<>();
+ private static final long TEXT_TRACK_NONE_ID = -1;
+ private int mSelectedTextPosition = 0;
+ private int mSelectedAudioPosition = -1;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.custom_tracks_dialog_layout, null);
+ setUpView(view);
+
+ builder.setView(view)
+ .setPositiveButton(getString(R.string.ccl_ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ List selectedTracks = new ArrayList<>();
+ MediaTrack textTrack = mTextAdapter.getSelectedTrack();
+ if (textTrack.getId() != TEXT_TRACK_NONE_ID) {
+ selectedTracks.add(textTrack);
+ }
+ MediaTrack audioVideoTrack = mAudioVideoAdapter.getSelectedTrack();
+ if (audioVideoTrack != null) {
+ selectedTracks.add(audioVideoTrack);
+ }
+ mCastManager.notifyTracksSelectedListeners(selectedTracks);
+ TracksChooserDialog.this.getDialog().cancel();
+ }
+ })
+ .setNegativeButton(R.string.ccl_cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ TracksChooserDialog.this.getDialog().cancel();
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ TracksChooserDialog.this.getDialog().cancel();
+ }
+ });
+
+ return builder.create();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ Bundle mediaWrapper = getArguments().getBundle(VideoCastManager.EXTRA_MEDIA);
+ mMediaInfo = Utils.bundleToMediaInfo(mediaWrapper);
+ mCastManager = VideoCastManager.getInstance();
+ mActiveTracks = mCastManager.getActiveTrackIds();
+ List allTracks = mMediaInfo.getMediaTracks();
+ if (allTracks == null || allTracks.isEmpty()) {
+ Utils.showToast(getActivity(), R.string.ccl_caption_no_tracks_available);
+ dismiss();
+ }
+ }
+
+ /**
+ * This is to get around the following bug:
+ * https://code.google.com/p/android/issues/detail?id=17423
+ */
+ @Override
+ public void onDestroyView() {
+ if (getDialog() != null && getRetainInstance()) {
+ getDialog().setDismissMessage(null);
+ }
+ super.onDestroyView();
+ }
+
+ private void setUpView(View view) {
+ ListView listView1 = (ListView) view.findViewById(R.id.listview1);
+ ListView listView2 = (ListView) view.findViewById(R.id.listview2);
+ TextView textEmptyMessageView = (TextView) view.findViewById(R.id.text_empty_message);
+ TextView audioEmptyMessageView = (TextView) view.findViewById(R.id.audio_empty_message);
+ partitionTracks();
+
+ mTextAdapter = new TracksListAdapter(getActivity(), R.layout.tracks_row_layout,
+ mTextTracks, mSelectedTextPosition);
+ mAudioVideoAdapter = new TracksListAdapter(getActivity(), R.layout.tracks_row_layout,
+ mAudioTracks, mSelectedAudioPosition);
+
+ listView1.setAdapter(mTextAdapter);
+ listView2.setAdapter(mAudioVideoAdapter);
+
+ TabHost tabs = (TabHost) view.findViewById(R.id.tabhost);
+ tabs.setup();
+
+ // create tab 1
+ TabHost.TabSpec tab1 = tabs.newTabSpec("tab1");
+ if (mTextTracks == null || mTextTracks.isEmpty()) {
+ listView1.setVisibility(View.INVISIBLE);
+ tab1.setContent(R.id.text_empty_message);
+ } else {
+ textEmptyMessageView.setVisibility(View.INVISIBLE);
+ tab1.setContent(R.id.listview1);
+ }
+ tab1.setIndicator(getString(R.string.ccl_caption_subtitles));
+ tabs.addTab(tab1);
+
+ // create tab 2
+ TabHost.TabSpec tab2 = tabs.newTabSpec("tab2");
+ if (mAudioTracks == null || mAudioTracks.isEmpty()) {
+ listView2.setVisibility(View.INVISIBLE);
+ tab2.setContent(R.id.audio_empty_message);
+ } else {
+ audioEmptyMessageView.setVisibility(View.INVISIBLE);
+ tab2.setContent(R.id.listview2);
+ }
+ tab2.setIndicator(getString(R.string.ccl_caption_audio));
+ tabs.addTab(tab2);
+ }
+
+ private MediaTrack buildNoneTrack() {
+ return new MediaTrack.Builder(TEXT_TRACK_NONE_ID, MediaTrack.TYPE_TEXT)
+ .setName(getString(R.string.ccl_none))
+ .setSubtype(MediaTrack.SUBTYPE_CAPTIONS)
+ .setContentId("").build();
+ }
+
+ /**
+ * This method loops through the tracks and partitions them into a group of Text tracks and a
+ * group of Audio tracks, and skips over the Video tracks.
+ */
+ private void partitionTracks() {
+ List allTracks = mMediaInfo.getMediaTracks();
+ mAudioTracks.clear();
+ mTextTracks.clear();
+ mTextTracks.add(buildNoneTrack());
+ mSelectedTextPosition = 0;
+ mSelectedAudioPosition = -1;
+ if (allTracks != null) {
+ int textPosition = 1; /* start from 1 since we have a NONE selection at the beginning */
+ int audioPosition = 0;
+ for (MediaTrack track : allTracks) {
+ switch (track.getType()) {
+ case MediaTrack.TYPE_TEXT:
+ mTextTracks.add(track);
+ if (mActiveTracks != null) {
+ for (long mActiveTrack : mActiveTracks) {
+ if (mActiveTrack == track.getId()) {
+ mSelectedTextPosition = textPosition;
+ }
+ }
+ }
+ textPosition++;
+ break;
+ case MediaTrack.TYPE_AUDIO:
+ mAudioTracks.add(track);
+ if (mActiveTracks != null) {
+ for (long mActiveTrack : mActiveTracks) {
+ if (mActiveTrack == track.getId()) {
+ mSelectedAudioPosition = audioPosition;
+ }
+ }
+ }
+ audioPosition++;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Call this static method to create a new instance of the dialog.
+ */
+ public static TracksChooserDialog newInstance(MediaInfo mediaInfo) {
+ TracksChooserDialog fragment = new TracksChooserDialog();
+ Bundle bundle = new Bundle();
+ bundle.putBundle(VideoCastManager.EXTRA_MEDIA, Utils.mediaInfoToBundle(mediaInfo));
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksListAdapter.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksListAdapter.java
new file mode 100644
index 000000000..a36e8d1dd
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/tracks/ui/TracksListAdapter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.cast.tracks.ui;
+
+import com.google.android.gms.cast.MediaTrack;
+import com.google.android.libraries.cast.companionlibrary.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An {@link android.widget.ArrayAdapter} for presenting tracks.
+ */
+public class TracksListAdapter extends ArrayAdapter
+ implements View.OnClickListener {
+
+ private final List mTracks;
+ private final Context mContext;
+ private int mSelectedPosition = -1;
+
+ public TracksListAdapter(Context context, int resource, List tracks,
+ int activePosition) {
+ super(context, resource);
+ this.mContext = context;
+ mTracks = new ArrayList<>();
+ mTracks.addAll(tracks);
+ mSelectedPosition = activePosition;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Holder holder;
+
+ if (convertView == null) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Activity.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.tracks_row_layout, parent, false);
+
+ holder = new Holder((TextView) convertView.findViewById(R.id.text),
+ (RadioButton) convertView.findViewById(R.id.radio));
+ convertView.setTag(holder);
+ } else {
+ holder = (Holder) convertView.getTag();
+ }
+ holder.radio.setTag(position);
+ holder.radio.setChecked(mSelectedPosition == position);
+ convertView.setOnClickListener(this);
+ holder.label.setText(mTracks.get(position).getName());
+ return convertView;
+ }
+
+ @Override
+ public int getCount() {
+ return mTracks == null ? 0 : mTracks.size();
+ }
+
+ @Override
+ public void onClick(View v) {
+ Holder holder = (Holder) v.getTag();
+ mSelectedPosition = (Integer) holder.radio.getTag();
+ notifyDataSetChanged();
+ }
+
+ private class Holder {
+
+ private final TextView label;
+ private final RadioButton radio;
+
+ private Holder(TextView label, RadioButton radio) {
+ this.label = label;
+ this.radio = radio;
+ }
+ }
+
+ public MediaTrack getSelectedTrack() {
+ if (mSelectedPosition >= 0) {
+ return mTracks.get(mSelectedPosition);
+ }
+ return null;
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/notification/VideoCastNotificationService.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/notification/VideoCastNotificationService.java
new file mode 100644
index 000000000..dc9bb7016
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/notification/VideoCastNotificationService.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.notification;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity;
+import com.google.android.libraries.cast.companionlibrary.utils.FetchBitmapTask;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v7.app.NotificationCompat;
+
+/**
+ * A service to provide status bar Notifications when we are casting. For JB+ versions, notification
+ * area provides a play/pause toggle and an "x" button to disconnect but that for GB, we do not
+ * show that due to the framework limitations.
+ */
+public class VideoCastNotificationService extends Service {
+
+ private static final String TAG = LogUtils.makeLogTag(VideoCastNotificationService.class);
+
+ public static final String ACTION_TOGGLE_PLAYBACK =
+ "com.google.android.libraries.cast.companionlibrary.action.toggleplayback";
+ public static final String ACTION_STOP =
+ "com.google.android.libraries.cast.companionlibrary.action.stop";
+ public static final String ACTION_VISIBILITY =
+ "com.google.android.libraries.cast.companionlibrary.action.notificationvisibility";
+ private static final int NOTIFICATION_ID = 1;
+ public static final String NOTIFICATION_VISIBILITY = "visible";
+
+ private Bitmap mVideoArtBitmap;
+ private boolean mIsPlaying;
+ private Class> mTargetActivity;
+ private int mOldStatus = -1;
+ private Notification mNotification;
+ private boolean mVisible;
+ private VideoCastManager mCastManager;
+ private VideoCastConsumerImpl mConsumer;
+ private FetchBitmapTask mBitmapDecoderTask;
+ private int mDimensionInPixels;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mDimensionInPixels = Utils.convertDpToPixel(VideoCastNotificationService.this,
+ getResources().getDimension(R.dimen.ccl_notification_image_size));
+ mCastManager = VideoCastManager.getInstance();
+ readPersistedData();
+ if (!mCastManager.isConnected() && !mCastManager.isConnecting()) {
+ mCastManager.reconnectSessionIfPossible();
+ }
+ mConsumer = new VideoCastConsumerImpl() {
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ LOGD(TAG, "onApplicationDisconnected() was reached, stopping the notification"
+ + " service");
+ stopSelf();
+ }
+
+ @Override
+ public void onRemoteMediaPlayerStatusUpdated() {
+ int mediaStatus = mCastManager.getPlaybackStatus();
+ VideoCastNotificationService.this.onRemoteMediaPlayerStatusUpdated(mediaStatus);
+ }
+
+ @Override
+ public void onUiVisibilityChanged(boolean visible) {
+ mVisible = !visible;
+ if (mVisible && (mNotification != null)) {
+ startForeground(NOTIFICATION_ID, mNotification);
+ } else {
+ stopForeground(true);
+ }
+ }
+ };
+ mCastManager.addVideoCastConsumer(mConsumer);
+
+ }
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LOGD(TAG, "onStartCommand");
+ if (intent != null) {
+
+ String action = intent.getAction();
+ if (ACTION_VISIBILITY.equals(action)) {
+ mVisible = intent.getBooleanExtra(NOTIFICATION_VISIBILITY, false);
+ LOGD(TAG, "onStartCommand(): Action: ACTION_VISIBILITY " + mVisible);
+ onRemoteMediaPlayerStatusUpdated(mCastManager.getPlaybackStatus());
+ if (mNotification == null) {
+ try {
+ setUpNotification(mCastManager.getRemoteMediaInformation());
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "onStartCommand() failed to get media", e);
+ }
+ }
+ if (mVisible && mNotification != null) {
+ startForeground(NOTIFICATION_ID, mNotification);
+ } else {
+ stopForeground(true);
+ }
+ } else {
+ LOGD(TAG, "onStartCommand(): Action: none");
+ }
+
+ } else {
+ LOGD(TAG, "onStartCommand(): Intent was null");
+ }
+
+ return Service.START_STICKY;
+ }
+
+ private void setUpNotification(final MediaInfo info)
+ throws TransientNetworkDisconnectionException, NoConnectionException {
+ if (info == null) {
+ return;
+ }
+ if (mBitmapDecoderTask != null) {
+ mBitmapDecoderTask.cancel(false);
+ }
+ Uri imgUri = null;
+ try {
+ if (!info.getMetadata().hasImages()) {
+ build(info, null, mIsPlaying);
+ return;
+ } else {
+ imgUri = info.getMetadata().getImages().get(0).getUrl();
+ }
+ } catch (CastException e) {
+ LOGE(TAG, "Failed to build notification", e);
+ }
+ mBitmapDecoderTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ try {
+ mVideoArtBitmap = Utils.scaleAndCenterCropBitmap(bitmap, mDimensionInPixels,
+ mDimensionInPixels);
+ build(info, mVideoArtBitmap, mIsPlaying);
+ } catch (CastException | NoConnectionException
+ | TransientNetworkDisconnectionException e) {
+ LOGE(TAG, "Failed to set notification for " + info.toString(), e);
+ }
+ if (mVisible && (mNotification != null)) {
+ startForeground(NOTIFICATION_ID, mNotification);
+ }
+ if (this == mBitmapDecoderTask) {
+ mBitmapDecoderTask = null;
+ }
+ }
+ };
+ mBitmapDecoderTask.execute(imgUri);
+ }
+
+ /**
+ * Removes the existing notification.
+ */
+ private void removeNotification() {
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).
+ cancel(NOTIFICATION_ID);
+ }
+
+ private void onRemoteMediaPlayerStatusUpdated(int mediaStatus) {
+ if (mOldStatus == mediaStatus) {
+ // not need to make any updates here
+ return;
+ }
+ mOldStatus = mediaStatus;
+ LOGD(TAG, "onRemoteMediaPlayerStatusUpdated() reached with status: " + mediaStatus);
+ try {
+ switch (mediaStatus) {
+ case MediaStatus.PLAYER_STATE_BUFFERING: // (== 4)
+ mIsPlaying = false;
+ setUpNotification(mCastManager.getRemoteMediaInformation());
+ break;
+ case MediaStatus.PLAYER_STATE_PLAYING: // (== 2)
+ mIsPlaying = true;
+ setUpNotification(mCastManager.getRemoteMediaInformation());
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED: // (== 3)
+ mIsPlaying = false;
+ setUpNotification(mCastManager.getRemoteMediaInformation());
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE: // (== 1)
+ mIsPlaying = false;
+ if (!mCastManager.shouldRemoteUiBeVisible(mediaStatus,
+ mCastManager.getIdleReason())) {
+ stopForeground(true);
+ } else {
+ setUpNotification(mCastManager.getRemoteMediaInformation());
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_UNKNOWN: // (== 0)
+ mIsPlaying = false;
+ stopForeground(true);
+ break;
+ default:
+ break;
+ }
+ } catch (TransientNetworkDisconnectionException | NoConnectionException e) {
+ LOGE(TAG, "Failed to update the playback status due to network issues", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ if (mBitmapDecoderTask != null) {
+ mBitmapDecoderTask.cancel(false);
+ }
+ removeNotification();
+ if (mCastManager != null && mConsumer != null) {
+ mCastManager.removeVideoCastConsumer(mConsumer);
+ mCastManager = null;
+ }
+ }
+
+ /*
+ * Build the RemoteViews for the notification. We also need to add the appropriate "back stack"
+ * so when user goes into the CastPlayerActivity, she can have a meaningful "back" experience.
+ */
+ private void build(MediaInfo info, Bitmap bitmap, boolean isPlaying)
+ throws CastException, TransientNetworkDisconnectionException, NoConnectionException {
+
+ // Playback PendingIntent
+ Intent playbackIntent = new Intent(ACTION_TOGGLE_PLAYBACK);
+ playbackIntent.setPackage(getPackageName());
+ PendingIntent playbackPendingIntent = PendingIntent
+ .getBroadcast(this, 0, playbackIntent, 0);
+
+ // Disconnect PendingIntent
+ Intent stopIntent = new Intent(ACTION_STOP);
+ stopIntent.setPackage(getPackageName());
+ PendingIntent stopPendingIntent = PendingIntent.getBroadcast(this, 0, stopIntent, 0);
+
+ // Main Content PendingIntent
+ Bundle mediaWrapper = Utils.mediaInfoToBundle(mCastManager.getRemoteMediaInformation());
+ Intent contentIntent = new Intent(this, mTargetActivity);
+ contentIntent.putExtra(VideoCastManager.EXTRA_MEDIA, mediaWrapper);
+
+ // Media metadata
+ MediaMetadata metadata = info.getMetadata();
+ String castingTo = getResources().getString(R.string.ccl_casting_to_device,
+ mCastManager.getDeviceName());
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addParentStack(mTargetActivity);
+ stackBuilder.addNextIntent(contentIntent);
+ if (stackBuilder.getIntentCount() > 1) {
+ stackBuilder.editIntentAt(1).putExtra(VideoCastManager.EXTRA_MEDIA, mediaWrapper);
+ }
+ PendingIntent contentPendingIntent =
+ stackBuilder.getPendingIntent(NOTIFICATION_ID, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ int pauseOrStopResourceId = 0;
+ if (info.getStreamType() == MediaInfo.STREAM_TYPE_LIVE) {
+ pauseOrStopResourceId = R.drawable.ic_notification_stop_48dp;
+ } else {
+ pauseOrStopResourceId = R.drawable.ic_notification_pause_48dp;
+ }
+ int pauseOrPlayTextResourceId = isPlaying ? R.string.ccl_pause : R.string.ccl_play;
+
+ NotificationCompat.Builder builder
+ = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_stat_action_notification)
+ .setContentTitle(metadata.getString(MediaMetadata.KEY_TITLE))
+ .setContentText(castingTo)
+ .setContentIntent(contentPendingIntent)
+ .setLargeIcon(bitmap)
+ .addAction(isPlaying ? pauseOrStopResourceId
+ : R.drawable.ic_notification_play_48dp,
+ getString(pauseOrPlayTextResourceId), playbackPendingIntent)
+ .addAction(R.drawable.ic_notification_disconnect_24dp,
+ getString(R.string.ccl_disconnect),
+ stopPendingIntent)
+ .setStyle(new NotificationCompat.MediaStyle()
+ .setShowActionsInCompactView(0, 1)
+ .setMediaSession(mCastManager.getMediaSessionCompatToken()))
+ .setOngoing(true)
+ .setShowWhen(false)
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
+
+
+ mNotification = builder.build();
+
+ }
+
+ private void togglePlayback() {
+ try {
+ mCastManager.togglePlayback();
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to toggle the playback", e);
+ }
+ }
+
+ /*
+ * We try to disconnect application but even if that fails, we need to remove notification since
+ * that is the only way to get rid of it without going to the application
+ */
+ private void stopApplication() {
+ try {
+ LOGD(TAG, "Calling stopApplication");
+ mCastManager.disconnect();
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to disconnect application", e);
+ }
+ stopSelf();
+ }
+
+ /*
+ * Reads application ID and target activity from preference storage.
+ */
+ private void readPersistedData() {
+ String targetName = mCastManager.getPreferenceAccessor().getStringFromPreference(
+ VideoCastManager.PREFS_KEY_CAST_ACTIVITY_NAME);
+ try {
+ if (targetName != null) {
+ mTargetActivity = Class.forName(targetName);
+ } else {
+ mTargetActivity = VideoCastControllerActivity.class;
+ }
+
+ } catch (ClassNotFoundException e) {
+ LOGE(TAG, "Failed to find the targetActivity class", e);
+ }
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/remotecontrol/VideoIntentReceiver.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/remotecontrol/VideoIntentReceiver.java
new file mode 100644
index 000000000..604ac6df3
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/remotecontrol/VideoIntentReceiver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.remotecontrol;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions
+ .TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.notification.VideoCastNotificationService;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+/**
+ * A {@link BroadcastReceiver} for receiving media button actions (from the lock screen) as well as
+ * the the status bar notification media actions.
+ */
+public class VideoIntentReceiver extends BroadcastReceiver {
+
+ private static final String TAG = LogUtils.makeLogTag(VideoIntentReceiver.class);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ VideoCastManager castMgr = VideoCastManager.getInstance();
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ switch (action) {
+ case VideoCastNotificationService.ACTION_TOGGLE_PLAYBACK:
+ try {
+ VideoCastManager.getInstance().togglePlayback();
+ } catch (CastException | TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ LOGE(TAG, "onReceive() Failed to toggle playback ");
+ }
+ break;
+ case VideoCastNotificationService.ACTION_STOP:
+ LOGD(TAG, "Calling stopApplication from intent");
+ castMgr.disconnect();
+ break;
+ case Intent.ACTION_MEDIA_BUTTON:
+ // this is used when we toggle playback from lockscreen in versions prior to
+ // Lollipop
+ if (!intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
+ return;
+ }
+ KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
+ if (keyEvent.getAction() != KeyEvent.ACTION_DOWN) {
+ return;
+ }
+
+ if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+ try {
+ VideoCastManager.getInstance().togglePlayback();
+ } catch (CastException | TransientNetworkDisconnectionException |
+ NoConnectionException e) {
+ LOGE(TAG, "onReceive() Failed to toggle playback ");
+ }
+ }
+ break;
+ }
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/FetchBitmapTask.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/FetchBitmapTask.java
new file mode 100644
index 000000000..404e2dd26
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/FetchBitmapTask.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.utils;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * An AsyncTask to fetch an image over HTTP and scale it to the desired size. Clients need to extend
+ * this and implement their own {@code onPostExecute(Bitmap bitmap)} method. It provides a uniform
+ * treatment of ThreadPool across various versions of Android.
+ */
+public abstract class FetchBitmapTask extends AsyncTask {
+ private final int mPreferredWidth;
+ private final int mPreferredHeight;
+
+ /**
+ * Constructs a new FetchBitmapTask that will do scaling.
+ *
+ * @param preferredWidth The preferred image width.
+ * @param preferredHeight The preferred image height.
+ */
+ public FetchBitmapTask(int preferredWidth, int preferredHeight) {
+ mPreferredWidth = preferredWidth;
+ mPreferredHeight = preferredHeight;
+ }
+
+ /**
+ * Constructs a new FetchBitmapTask. No scaling will be performed if you use this constructor.
+ */
+ public FetchBitmapTask() {
+ this(0, 0);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Uri... uris) {
+ if (uris.length != 1 || uris[0] == null) {
+ return null;
+ }
+
+ Bitmap bitmap = null;
+ URL url;
+ try {
+ url = new URL(uris[0].toString());
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ HttpURLConnection urlConnection = null;
+ try {
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setDoInput(true);
+
+ if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ InputStream stream = new BufferedInputStream(urlConnection.getInputStream());
+ bitmap = BitmapFactory.decodeStream(stream);
+ if ((mPreferredWidth > 0) && (mPreferredHeight > 0)) {
+ bitmap = scaleBitmap(bitmap);
+ }
+ }
+ } catch (IOException e) { /* ignore */
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Executes the task.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void execute(Uri uri) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri);
+ } else {
+ execute(new Uri[] {uri});
+ }
+ }
+
+ /*
+ * Scales the bitmap to the preferred width and height.
+ *
+ * @param bitmap The bitmap to scale.
+ * @return The scaled bitmap.
+ */
+ private Bitmap scaleBitmap(Bitmap bitmap) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+
+ // Calculate deltas.
+ int dw = width - mPreferredWidth;
+ int dh = height - mPreferredHeight;
+
+ if ((dw == 0) && (dh == 0)) {
+ return bitmap;
+ }
+
+ float scaleFactor;
+ if ((dw > 0) || (dh > 0)) {
+ // Icon is too big; scale down.
+ float scaleWidth = (float) mPreferredWidth / width;
+ float scaleHeight = (float) mPreferredHeight / height;
+ scaleFactor = Math.min(scaleHeight, scaleWidth);
+ } else {
+ // Icon is too small; scale up.
+ float scaleWidth = width / (float) mPreferredWidth;
+ float scaleHeight = height / (float) mPreferredHeight;
+ scaleFactor = Math.min(scaleHeight, scaleWidth);
+ }
+
+ int finalWidth = (int) ((width * scaleFactor) + 0.5f);
+ int finalHeight = (int) ((height * scaleFactor) + 0.5f);
+
+ return Bitmap.createScaledBitmap(bitmap, finalWidth, finalHeight, false);
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/LogUtils.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/LogUtils.java
new file mode 100644
index 000000000..1858614a3
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/LogUtils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.utils;
+
+import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
+
+import android.util.Log;
+
+/**
+ * Provides a simple wrapper to control logging in development vs production environment. This
+ * library should only use the wrapper methods that this class provides.
+ */
+public class LogUtils {
+
+ private static final String LOG_PREFIX = "ccl_";
+ private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();
+ private static final int MAX_LOG_TAG_LENGTH = 23;
+
+ private static final boolean DEBUG = false;
+
+ private LogUtils() {
+ }
+
+ public static String makeLogTag(String str) {
+ if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
+ return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
+ }
+
+ return LOG_PREFIX + str;
+ }
+
+ /**
+ * WARNING: Don't use this when obfuscating class names with Proguard!
+ */
+ public static String makeLogTag(Class> cls) {
+ return makeLogTag(cls.getSimpleName());
+ }
+
+ @SuppressWarnings("unused")
+ public static final void LOGD(final String tag, String message) {
+ if (DEBUG || Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, getVersionPrefix() + message);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static final void LOGD(final String tag, String message, Throwable cause) {
+ if (DEBUG || Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, getVersionPrefix() + message, cause);
+ }
+ }
+
+ public static final void LOGV(final String tag, String message) {
+ if (DEBUG && Log.isLoggable(tag, Log.VERBOSE)) {
+ Log.v(tag, getVersionPrefix() + message);
+ }
+ }
+
+ public static final void LOGV(final String tag, String message, Throwable cause) {
+ if (DEBUG && Log.isLoggable(tag, Log.VERBOSE)) {
+ Log.v(tag, getVersionPrefix() + message, cause);
+ }
+ }
+
+ public static final void LOGI(final String tag, String message) {
+ Log.i(tag, getVersionPrefix() + message);
+ }
+
+ public static final void LOGI(final String tag, String message, Throwable cause) {
+ Log.i(tag, message, cause);
+ }
+
+ public static final void LOGW(final String tag, String message) {
+ Log.w(tag, getVersionPrefix() + message);
+ }
+
+ public static final void LOGW(final String tag, String message, Throwable cause) {
+ Log.w(tag, getVersionPrefix() + message, cause);
+ }
+
+ public static final void LOGE(final String tag, String message) {
+ Log.e(tag, getVersionPrefix() + message);
+ }
+
+ public static final void LOGE(final String tag, String message, Throwable cause) {
+ Log.e(tag, getVersionPrefix() + message, cause);
+ }
+
+ public static final String getVersionPrefix() {
+ return "[v" + BaseCastManager.getCclVersion() + "] ";
+ }
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/PreferenceAccessor.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/PreferenceAccessor.java
new file mode 100644
index 000000000..b329c1a6f
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/PreferenceAccessor.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+/**
+ * A class to streamline access to the Preference storage for both reading and writing.
+ */
+public class PreferenceAccessor {
+ private final SharedPreferences mSharedPreference;
+
+ public PreferenceAccessor(Context context) {
+ mSharedPreference = PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+
+ /**
+ * Saves a string value under the provided key in the preference manager. If value
+ * is null
, then the provided key will be removed from the preferences.
+ */
+ public void saveStringToPreference(String key, String value) {
+ if (value == null) {
+ // we want to remove
+ mSharedPreference.edit().remove(key).apply();
+ } else {
+ mSharedPreference.edit().putString(key, value).apply();
+ }
+ }
+
+ /**
+ * Saves a float value under the provided key in the preference manager. If {@code value}
+ * is {@code null}, then the provided key will be removed from the preferences.
+ */
+ public void saveFloatToPreference(String key, Float value) {
+ if (value == null) {
+ // we want to remove
+ mSharedPreference.edit().remove(key).apply();
+ } else {
+ mSharedPreference.edit().putFloat(key, value).apply();
+ }
+ }
+
+ /**
+ * Saves an integer value under the provided key in the preference manager. If {@code value}
+ * is {@code null}, then the provided key will be removed from the preferences.
+ */
+ public void saveIntToPreference(String key, Integer value) {
+ if (value == null) {
+ // we want to remove
+ mSharedPreference.edit().remove(key).apply();
+ } else {
+ mSharedPreference.edit().putInt(key, value).apply();
+ }
+ }
+
+ /**
+ * Saves a long value under the provided key in the preference manager. If {@code value}
+ * is {@code null}, then the provided key will be removed from the preferences.
+ */
+ public void saveLongToPreference(String key, Long value) {
+ if (value == null) {
+ // we want to remove
+ mSharedPreference.edit().remove(key).apply();
+ } else {
+ mSharedPreference.edit().putLong(key, value).apply();
+ }
+
+ }
+
+ /**
+ * Saves a boolean value under the provided key in the preference manager. If value
+ * is null
, then the provided key will be removed from the preferences.
+ */
+ public void saveBooleanToPreference(String key, Boolean value) {
+ if (value == null) {
+ // we want to remove
+ mSharedPreference.edit().remove(key).apply();
+ } else {
+ mSharedPreference.edit().putBoolean(key, value).apply();
+ }
+ }
+
+ /**
+ * Retrieves a String value from preference manager. If no such key exists, it will return
+ * null
.
+ */
+ public String getStringFromPreference(String key) {
+ return getStringFromPreference(key, null);
+ }
+
+ /**
+ * Retrieves a String value from preference manager. If no such key exists, it will return the
+ * defaultValue
.
+ */
+ public String getStringFromPreference(String key, String defaultValue) {
+ return mSharedPreference.getString(key, defaultValue);
+ }
+
+ /**
+ * Retrieves a float value from preference manager. If no such key exists, it will return
+ * Float.MIN_VALUE
.
+ */
+ public float getFloatFromPreference(String key) {
+ return mSharedPreference.getFloat(key, Float.MIN_VALUE);
+ }
+
+ /**
+ * Retrieves an integer value from preference manager. If no such key exists, it will return
+ * Integer.MIN_VALUE
.
+ */
+ public int getIntFromPreference(String key) {
+ return mSharedPreference.getInt(key, Integer.MIN_VALUE);
+ }
+
+ /**
+ * Retrieves an integer value from preference manager. If no such key exists, it will return
+ * value provided by the {@code defaultValue}.
+ */
+ public int getIntFromPreference(String key, int defaultValue) {
+ return mSharedPreference.getInt(key, defaultValue);
+ }
+
+ /**
+ * Retrieves a long value from preference manager. If no such key exists, it will return the
+ * value provided as defaultValue
+ */
+ public long getLongFromPreference(String key, long defaultValue) {
+ return mSharedPreference.getLong(key, defaultValue);
+ }
+
+ /**
+ * Retrieves a boolean value from preference manager. If no such key exists, it will return the
+ * value provided as defaultValue
+ */
+ public boolean getBooleanFromPreference(String key, boolean defaultValue) {
+ return mSharedPreference.getBoolean(key, defaultValue);
+ }
+
+
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/Utils.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/Utils.java
new file mode 100644
index 000000000..ae3b45a80
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/utils/Utils.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.utils;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.MediaTrack;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.images.WebImage;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * A collection of utility methods, all static.
+ */
+public final class Utils {
+
+ private static final String TAG = LogUtils.makeLogTag(Utils.class);
+ private static final String KEY_MEDIA_TYPE = "media-type";
+ private static final String KEY_IMAGES = "images";
+ private static final String KEY_URL = "movie-urls";
+ private static final String KEY_CONTENT_TYPE = "content-type";
+ private static final String KEY_STREAM_TYPE = "stream-type";
+ private static final String KEY_CUSTOM_DATA = "custom-data";
+ private static final String KEY_STREAM_DURATION = "stream-duration";
+ private static final String KEY_TRACK_ID = "track-id";
+ private static final String KEY_TRACK_CONTENT_ID = "track-custom-id";
+ private static final String KEY_TRACK_NAME = "track-name";
+ private static final String KEY_TRACK_TYPE = "track-type";
+ private static final String KEY_TRACK_SUBTYPE = "track-subtype";
+ private static final String KEY_TRACK_LANGUAGE = "track-language";
+ private static final String KEY_TRACK_CUSTOM_DATA = "track-custom-data";
+ private static final String KEY_TRACKS_DATA = "track-data";
+ public static final boolean IS_KITKAT_OR_ABOVE =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ public static final boolean IS_ICS_OR_ABOVE =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ private Utils() {
+ }
+
+ /**
+ * Formats time from milliseconds to hh:mm:ss string format.
+ */
+ public static String formatMillis(int millisec) {
+ int seconds = (int) (millisec / 1000);
+ int hours = seconds / (60 * 60);
+ seconds %= (60 * 60);
+ int minutes = seconds / 60;
+ seconds %= 60;
+
+ String time;
+ if (hours > 0) {
+ time = String.format("%d:%02d:%02d", hours, minutes, seconds);
+ } else {
+ time = String.format("%d:%02d", minutes, seconds);
+ }
+ return time;
+ }
+
+ /**
+ * Shows a (long) toast.
+ */
+ public static void showToast(Context context, int resourceId) {
+ Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * Returns the URL of an image for the {@link MediaInfo} at the given index. Index should be a
+ * number between 0 and {@code n-1} where {@code n} is the number of images for that given item.
+ */
+ public static String getImageUrl(MediaInfo info, int index) {
+ Uri uri = getImageUri(info, index);
+ if (uri != null) {
+ return uri.toString();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@code Uri} address of an image for the {@link MediaInfo} at the given
+ * index. Index should be a number between 0 and {@code n - 1} where {@code n} is the
+ * number of images for that given item.
+ */
+ public static Uri getImageUri(MediaInfo info, int index) {
+ MediaMetadata mediaMetadata = info.getMetadata();
+ if (mediaMetadata != null && mediaMetadata.getImages().size() > index) {
+ return mediaMetadata.getImages().get(index).getUrl();
+ }
+ return null;
+ }
+
+ /**
+ * A utility method to validate that the appropriate version of the Google Play Services is
+ * available on the device. If not, it will open a dialog to address the issue. The dialog
+ * displays a localized message about the error and upon user confirmation (by tapping on
+ * dialog) will direct them to the Play Store if Google Play services is out of date or
+ * missing, or to system settings if Google Play services is disabled on the device.
+ */
+ public static boolean checkGooglePlayServices(final Activity activity) {
+ final int googlePlayServicesCheck = GooglePlayServicesUtil.isGooglePlayServicesAvailable(
+ activity);
+ switch (googlePlayServicesCheck) {
+ case ConnectionResult.SUCCESS:
+ return true;
+ default:
+ Dialog dialog = GooglePlayServicesUtil.getErrorDialog(googlePlayServicesCheck,
+ activity, 0);
+ dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ activity.finish();
+ }
+ });
+ dialog.show();
+ }
+ return false;
+ }
+
+ /**
+ * Builds and returns a {@link Bundle} which contains a select subset of data in the
+ * {@link MediaInfo}. Since {@link MediaInfo} is not {@link Parcelable}, one can use this
+ * container bundle to pass around from one activity to another.
+ *
+ * @see bundleToMediaInfo()
+ */
+ public static Bundle mediaInfoToBundle(MediaInfo info) {
+ if (info == null) {
+ return null;
+ }
+
+ MediaMetadata md = info.getMetadata();
+ Bundle wrapper = new Bundle();
+ wrapper.putString(MediaMetadata.KEY_TITLE, md.getString(MediaMetadata.KEY_TITLE));
+ wrapper.putString(MediaMetadata.KEY_SUBTITLE, md.getString(MediaMetadata.KEY_SUBTITLE));
+ wrapper.putString(MediaMetadata.KEY_ALBUM_TITLE,
+ md.getString(MediaMetadata.KEY_ALBUM_TITLE));
+ wrapper.putString(MediaMetadata.KEY_ALBUM_ARTIST,
+ md.getString(MediaMetadata.KEY_ALBUM_ARTIST));
+ wrapper.putString(MediaMetadata.KEY_COMPOSER, md.getString(MediaMetadata.KEY_COMPOSER));
+ Calendar releaseCalendar = md.getDate(MediaMetadata.KEY_RELEASE_DATE);
+ if (releaseCalendar != null) {
+ long releaseMillis = releaseCalendar.getTimeInMillis();
+ wrapper.putLong(MediaMetadata.KEY_RELEASE_DATE, releaseMillis);
+ }
+ wrapper.putInt(KEY_MEDIA_TYPE, info.getMetadata().getMediaType());
+ wrapper.putString(KEY_URL, info.getContentId());
+ wrapper.putString(MediaMetadata.KEY_STUDIO, md.getString(MediaMetadata.KEY_STUDIO));
+ wrapper.putString(KEY_CONTENT_TYPE, info.getContentType());
+ wrapper.putInt(KEY_STREAM_TYPE, info.getStreamType());
+ wrapper.putLong(KEY_STREAM_DURATION, info.getStreamDuration());
+ if (!md.getImages().isEmpty()) {
+ ArrayList urls = new ArrayList<>();
+ for (WebImage img : md.getImages()) {
+ urls.add(img.getUrl().toString());
+ }
+ wrapper.putStringArrayList(KEY_IMAGES, urls);
+ }
+ JSONObject customData = info.getCustomData();
+ if (customData != null) {
+ wrapper.putString(KEY_CUSTOM_DATA, customData.toString());
+ }
+ if (info.getMediaTracks() != null && !info.getMediaTracks().isEmpty()) {
+ try {
+ JSONArray jsonArray = new JSONArray();
+ for (MediaTrack mt : info.getMediaTracks()) {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put(KEY_TRACK_NAME, mt.getName());
+ jsonObject.put(KEY_TRACK_CONTENT_ID, mt.getContentId());
+ jsonObject.put(KEY_TRACK_ID, mt.getId());
+ jsonObject.put(KEY_TRACK_LANGUAGE, mt.getLanguage());
+ jsonObject.put(KEY_TRACK_TYPE, mt.getType());
+ if (mt.getSubtype() != MediaTrack.SUBTYPE_UNKNOWN) {
+ jsonObject.put(KEY_TRACK_SUBTYPE, mt.getSubtype());
+ }
+ if (mt.getCustomData() != null) {
+ jsonObject.put(KEY_TRACK_CUSTOM_DATA, mt.getCustomData().toString());
+ }
+ jsonArray.put(jsonObject);
+ }
+ wrapper.putString(KEY_TRACKS_DATA, jsonArray.toString());
+ } catch (JSONException e) {
+ LOGE(TAG, "mediaInfoToBundle(): Failed to convert Tracks data to json", e);
+ }
+ }
+
+ return wrapper;
+ }
+
+ /**
+ * Builds and returns a {@link MediaInfo} that was wrapped in a {@link Bundle} by
+ * mediaInfoToBundle
. It is assumed that the type of the {@link MediaInfo} is
+ * {@code MediaMetaData.MEDIA_TYPE_MOVIE}
+ *
+ * @see mediaInfoToBundle()
+ */
+ public static MediaInfo bundleToMediaInfo(Bundle wrapper) {
+ if (wrapper == null) {
+ return null;
+ }
+
+ MediaMetadata metaData = new MediaMetadata(wrapper.getInt(KEY_MEDIA_TYPE));
+
+ metaData.putString(MediaMetadata.KEY_SUBTITLE,
+ wrapper.getString(MediaMetadata.KEY_SUBTITLE));
+ metaData.putString(MediaMetadata.KEY_TITLE, wrapper.getString(MediaMetadata.KEY_TITLE));
+ metaData.putString(MediaMetadata.KEY_STUDIO, wrapper.getString(MediaMetadata.KEY_STUDIO));
+ metaData.putString(MediaMetadata.KEY_ALBUM_ARTIST,
+ wrapper.getString(MediaMetadata.KEY_ALBUM_ARTIST));
+ metaData.putString(MediaMetadata.KEY_ALBUM_TITLE,
+ wrapper.getString(MediaMetadata.KEY_ALBUM_TITLE));
+ metaData.putString(MediaMetadata.KEY_COMPOSER,
+ wrapper.getString(MediaMetadata.KEY_COMPOSER));
+
+ long releaseDateMillis = wrapper.getLong(MediaMetadata.KEY_RELEASE_DATE, 0);
+ if (releaseDateMillis > 0) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(releaseDateMillis);
+ metaData.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
+ }
+ ArrayList images = wrapper.getStringArrayList(KEY_IMAGES);
+ if (images != null && !images.isEmpty()) {
+ for (String url : images) {
+ Uri uri = Uri.parse(url);
+ metaData.addImage(new WebImage(uri));
+ }
+ }
+ String customDataStr = wrapper.getString(KEY_CUSTOM_DATA);
+ JSONObject customData = null;
+ if (!TextUtils.isEmpty(customDataStr)) {
+ try {
+ customData = new JSONObject(customDataStr);
+ } catch (JSONException e) {
+ LOGE(TAG, "Failed to deserialize the custom data string: custom data= "
+ + customDataStr);
+ }
+ }
+ List mediaTracks = null;
+ if (wrapper.getString(KEY_TRACKS_DATA) != null) {
+ try {
+ JSONArray jsonArray = new JSONArray(wrapper.getString(KEY_TRACKS_DATA));
+ mediaTracks = new ArrayList();
+ if (jsonArray.length() > 0) {
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject jsonObj = (JSONObject) jsonArray.get(i);
+ MediaTrack.Builder builder = new MediaTrack.Builder(
+ jsonObj.getLong(KEY_TRACK_ID), jsonObj.getInt(KEY_TRACK_TYPE));
+ if (jsonObj.has(KEY_TRACK_NAME)) {
+ builder.setName(jsonObj.getString(KEY_TRACK_NAME));
+ }
+ if (jsonObj.has(KEY_TRACK_SUBTYPE)) {
+ builder.setSubtype(jsonObj.getInt(KEY_TRACK_SUBTYPE));
+ }
+ if (jsonObj.has(KEY_TRACK_CONTENT_ID)) {
+ builder.setContentId(jsonObj.getString(KEY_TRACK_CONTENT_ID));
+ }
+ if (jsonObj.has(KEY_TRACK_LANGUAGE)) {
+ builder.setLanguage(jsonObj.getString(KEY_TRACK_LANGUAGE));
+ }
+ if (jsonObj.has(KEY_TRACKS_DATA)) {
+ builder.setCustomData(
+ new JSONObject(jsonObj.getString(KEY_TRACKS_DATA)));
+ }
+ mediaTracks.add(builder.build());
+ }
+ }
+ } catch (JSONException e) {
+ LOGE(TAG, "Failed to build media tracks from the wrapper bundle", e);
+ }
+ }
+ MediaInfo.Builder mediaBuilder = new MediaInfo.Builder(wrapper.getString(KEY_URL))
+ .setStreamType(wrapper.getInt(KEY_STREAM_TYPE))
+ .setContentType(wrapper.getString(KEY_CONTENT_TYPE))
+ .setMetadata(metaData)
+ .setCustomData(customData)
+ .setMediaTracks(mediaTracks);
+
+ if (wrapper.containsKey(KEY_STREAM_DURATION)
+ && wrapper.getLong(KEY_STREAM_DURATION) >= 0) {
+ mediaBuilder.setStreamDuration(wrapper.getLong(KEY_STREAM_DURATION));
+ }
+
+ return mediaBuilder.build();
+ }
+
+ /**
+ * Returns the SSID of the wifi connection, or null
if there is no wifi.
+ */
+ public static String getWifiSsid(Context context) {
+// WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+// WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+// if (wifiInfo != null) {
+// return wifiInfo.getSSID();
+// }
+
+ // This hack keeps Santa from depending on the ACCESS_WIFI_STATE permission
+ return null;
+ }
+
+ /**
+ * Scale and center-crop a bitmap to fit the given dimensions.
+ */
+ public static Bitmap scaleAndCenterCropBitmap(Bitmap source, int newHeight, int newWidth) {
+ if (source == null) {
+ return null;
+ }
+ int sourceWidth = source.getWidth();
+ int sourceHeight = source.getHeight();
+
+ float xScale = (float) newWidth / sourceWidth;
+ float yScale = (float) newHeight / sourceHeight;
+ float scale = Math.max(xScale, yScale);
+
+ float scaledWidth = scale * sourceWidth;
+ float scaledHeight = scale * sourceHeight;
+
+ float left = (newWidth - scaledWidth) / 2;
+ float top = (newHeight - scaledHeight) / 2;
+
+ RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
+
+ Bitmap destination = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
+ Canvas canvas = new Canvas(destination);
+ canvas.drawBitmap(source, null, targetRect, null);
+
+ return destination;
+ }
+
+ /**
+ * Converts DIP (or DP) to Pixels
+ */
+ public static int convertDpToPixel(Context context, float dp) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+ context.getResources().getDisplayMetrics());
+ }
+
+ /**
+ * Given a list of queue items, this method recreates an identical list of items except that the
+ * {@code itemId} of each item is erased, in effect preparing the list to be reloaded on the
+ * receiver.
+ */
+ public static MediaQueueItem[] rebuildQueue(List items) {
+ if (items == null || items.isEmpty()) {
+ return null;
+ }
+ MediaQueueItem[] rebuiltQueue = new MediaQueueItem[items.size()];
+ for (int i = 0; i < items.size(); i++) {
+ rebuiltQueue[i] = rebuildQueueItem(items.get(i));
+ }
+
+ return rebuiltQueue;
+ }
+
+ /**
+ * Given a list of queue items, and a new item, this method recreates an identical list of items
+ * from the queue, except that the {@code itemId} of each item is erased, in effect preparing
+ * the list to be reloaded. Then, it appends the new item to teh end of the rebuilt list and
+ * returns the result.
+ */
+ public static MediaQueueItem[] rebuildQueueAndAppend(List items,
+ MediaQueueItem currentItem) {
+ if (items == null || items.isEmpty()) {
+ return new MediaQueueItem[]{currentItem};
+ }
+ MediaQueueItem[] rebuiltQueue = new MediaQueueItem[items.size() + 1];
+ for (int i = 0; i < items.size(); i++) {
+ rebuiltQueue[i] = rebuildQueueItem(items.get(i));
+ }
+ rebuiltQueue[items.size()] = currentItem;
+
+ return rebuiltQueue;
+ }
+
+ /**
+ * Given a queue item, it returns an identical item except that the {@code itemId} has been
+ * cleared.
+ */
+ public static MediaQueueItem rebuildQueueItem(MediaQueueItem item) {
+ return new MediaQueueItem.Builder(item).clearItemId().build();
+ }
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/IMiniController.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/IMiniController.java
new file mode 100644
index 000000000..a1c409e58
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/IMiniController.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.widgets;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.widgets.MiniController.OnMiniControllerChangedListener;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+/**
+ * An interface to abstract {@link MiniController} so that other components can also control the
+ * MiniControllers. Clients should code against this interface when they want to control the
+ * provided {@link MiniController} or other custom implementations.
+ */
+public interface IMiniController {
+
+ /**
+ * Sets the uri for the album art
+ */
+ void setIcon(Uri uri);
+
+ /**
+ * Sets the bitmap for the album art
+ */
+ void setIcon(Bitmap bitmap);
+
+ /**
+ * Sets the title
+ */
+ void setTitle(String title);
+
+ /**
+ * Sets the subtitle
+ */
+ void setSubtitle(String subtitle);
+
+ /**
+ * Sets the playback state, and the idleReason (this is only reliable when the state is idle).
+ * Values that can be passed to this method are from {@link MediaStatus}
+ */
+ void setPlaybackStatus(int state, int idleReason);
+
+ /**
+ * Sets whether this component should be visible or hidden.
+ */
+ void setVisibility(int visibility);
+
+ /**
+ * Returns the visibility state of this widget
+ */
+ boolean isVisible();
+
+ /**
+ * Assigns a {@link OnMiniControllerChangedListener} listener to be notified of the changes in
+ * the mini controller
+ */
+ void setOnMiniControllerChangedListener(OnMiniControllerChangedListener listener);
+
+ /**
+ * Sets the type of stream. {@code streamType} can be {@link MediaInfo#STREAM_TYPE_LIVE}
+ * or {@link MediaInfo#STREAM_TYPE_BUFFERED}
+ */
+ void setStreamType(int streamType);
+
+ /**
+ * Sets the progress of stream.
+ */
+ void setProgress(int progress, int duration);
+
+ /**
+ * Sets the visibility of the progress indicator
+ */
+ void setProgressVisibility(boolean visible);
+
+ /**
+ * Sets whether the "upcoming" sub-component should be visible or not
+ */
+ void setUpcomingVisibility(boolean visible);
+
+ /**
+ * Sets the upcoming item, which can be {@code null}.
+ */
+ void setUpcomingItem(MediaQueueItem item);
+
+ /**
+ * Controls the visibility of the currently playing item.
+ */
+ void setCurrentVisibility(boolean visible);
+
+
+}
diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/MiniController.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/MiniController.java
new file mode 100644
index 000000000..528764faa
--- /dev/null
+++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/widgets/MiniController.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2015 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.libraries.cast.companionlibrary.widgets;
+
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.MediaStatus;
+import com.google.android.libraries.cast.companionlibrary.R;
+import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.OnFailedListener;
+import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
+import com.google.android.libraries.cast.companionlibrary.utils.FetchBitmapTask;
+import com.google.android.libraries.cast.companionlibrary.utils.Utils;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * A compound component that provides a superset of functionalities required for the global access
+ * requirement. This component provides an image for the album art, a play/pause button, and a
+ * progressbar to show the current position. When an auto-play queue is playing and pre-loading is
+ * set, then this component can show an additional view to inform the user of the upcoming item and
+ * to allow immediate playback of the next item or to stop the auto-play.
+ *
+ * Clients can add this
+ * compound component to their layout xml and preferably set the {@code auto_setup} attribute to
+ * {@code true} to have the CCL manage the visibility and behavior of this component. Alternatively,
+ * clients can register this component with the instance of
+ * {@link VideoCastManager} by using the following pattern:
+ *
+ *
+ * mMiniController = (MiniController) findViewById(R.id.miniController);
+ * mCastManager.addMiniController(mMiniController);
+ * mMiniController.setOnMiniControllerChangedListener(mCastManager);
+ *
+ *
+ * In this case, clients should remember to unregister the component themselves.
+ * Then the {@link VideoCastManager} will manage the behavior, including its state and metadata and
+ * interactions. Note that using the {@code auto_setup} attribute hand;les all of these
+ * automatically.
+ */
+public class MiniController extends RelativeLayout implements IMiniController {
+
+ public static final int UNDEFINED_STATUS_CODE = -1;
+ private boolean mAutoSetup;
+ private VideoCastManager mCastManager;
+ private Handler mHandler;
+ protected ImageView mIcon;
+ protected TextView mTitle;
+ protected TextView mSubTitle;
+ protected ImageView mPlayPause;
+ protected ProgressBar mLoading;
+ private OnMiniControllerChangedListener mListener;
+ private Uri mIconUri;
+ private Drawable mPauseDrawable;
+ private Drawable mPlayDrawable;
+ private int mStreamType = MediaInfo.STREAM_TYPE_BUFFERED;
+ private Drawable mStopDrawable;
+ private FetchBitmapTask mFetchBitmapTask;
+ private ProgressBar mProgressBar;
+ private ImageView mUpcomingIcon;
+ private TextView mUpcomingTitle;
+ private View mUpcomingContainer;
+ private View mUpcomingPlay;
+ private View mUpcomingStop;
+ private Uri mUpcomingIconUri;
+ private FetchBitmapTask mFetchUpcomingBitmapTask;
+ private View mMainContainer;
+ private MediaQueueItem mUpcomingItem;
+
+ public MiniController(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.mini_controller, this);
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MiniController);
+ mAutoSetup = a.getBoolean(R.styleable.MiniController_auto_setup, false);
+ a.recycle();
+ mPauseDrawable = getResources().getDrawable(R.drawable.ic_mini_controller_pause);
+ mPlayDrawable = getResources().getDrawable(R.drawable.ic_mini_controller_play);
+ mStopDrawable = getResources().getDrawable(R.drawable.ic_mini_controller_stop);
+ mHandler = new Handler();
+ mCastManager = VideoCastManager.getInstance();
+ loadViews();
+ setUpCallbacks();
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ mProgressBar.setProgress(0);
+ }
+ }
+
+ /**
+ * Sets the listener that should be notified when a relevant event is fired from this
+ * component.
+ * Clients can register the {@link VideoCastManager} instance to be the default listener so it
+ * can control the remote media playback.
+ */
+ @Override
+ public void setOnMiniControllerChangedListener(OnMiniControllerChangedListener listener) {
+ if (listener != null) {
+ this.mListener = listener;
+ }
+ }
+
+ /**
+ * Removes the listener that was registered by
+ * {@link #setOnMiniControllerChangedListener(OnMiniControllerChangedListener)}
+ */
+ public void removeOnMiniControllerChangedListener(OnMiniControllerChangedListener listener) {
+ if ((listener != null) && (mListener == listener)) {
+ mListener = null;
+ }
+ }
+
+ @Override
+ public void setStreamType(int streamType) {
+ mStreamType = streamType;
+ }
+
+ @Override
+ public void setProgress(final int progress, final int duration) {
+ // for live streams, we do not attempt to update the progress bar
+ if (mStreamType == MediaInfo.STREAM_TYPE_LIVE || mProgressBar == null) {
+ return;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mProgressBar.setMax(duration);
+ mProgressBar.setProgress(progress);
+ }
+ });
+ }
+
+ @Override
+ public void setProgressVisibility(boolean visible) {
+ if (mProgressBar == null) {
+ return;
+ }
+ mProgressBar.setVisibility(
+ visible && (mStreamType != MediaInfo.STREAM_TYPE_LIVE) ? View.VISIBLE
+ : View.INVISIBLE);
+ }
+
+ @Override
+ public void setUpcomingVisibility(boolean visible) {
+ mUpcomingContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
+ setProgressVisibility(!visible);
+ }
+
+ @Override
+ public void setUpcomingItem(MediaQueueItem item) {
+ mUpcomingItem = item;
+ if (item != null) {
+ MediaInfo mediaInfo = item.getMedia();
+ if (mediaInfo != null) {
+ MediaMetadata metadata = mediaInfo.getMetadata();
+ setUpcomingTitle(metadata.getString(MediaMetadata.KEY_TITLE));
+ setUpcomingIcon(Utils.getImageUri(mediaInfo, 0));
+ }
+ } else {
+ setUpcomingTitle("");
+ setUpcomingIcon((Uri) null);
+ }
+ }
+
+ @Override
+ public void setCurrentVisibility(boolean visible) {
+ mMainContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ private void setUpCallbacks() {
+
+ mPlayPause.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ setLoadingVisibility(true);
+ try {
+ mListener.onPlayPauseClicked(v);
+ } catch (CastException e) {
+ mListener.onFailed(R.string.ccl_failed_perform_action,
+ UNDEFINED_STATUS_CODE);
+ } catch (TransientNetworkDisconnectionException e) {
+ mListener.onFailed(R.string.ccl_failed_no_connection_trans,
+ UNDEFINED_STATUS_CODE);
+ } catch (NoConnectionException e) {
+ mListener
+ .onFailed(R.string.ccl_failed_no_connection, UNDEFINED_STATUS_CODE);
+ }
+ }
+ }
+ });
+
+ mMainContainer.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ if (mListener != null) {
+ setLoadingVisibility(false);
+ try {
+ mListener.onTargetActivityInvoked(mIcon.getContext());
+ } catch (Exception e) {
+ mListener.onFailed(R.string.ccl_failed_perform_action, -1);
+ }
+ }
+
+ }
+ });
+
+ mUpcomingPlay.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onUpcomingPlayClicked(v, mUpcomingItem);
+ }
+ }
+ });
+
+ mUpcomingStop.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onUpcomingStopClicked(v, mUpcomingItem);
+ }
+ }
+ });
+ }
+
+ public MiniController(Context context) {
+ super(context);
+ loadViews();
+ }
+
+ @Override
+ public final void setIcon(Bitmap bm) {
+ mIcon.setImageBitmap(bm);
+ }
+
+ private void setUpcomingIcon(Bitmap bm) {
+ mUpcomingIcon.setImageBitmap(bm);
+ }
+
+ @Override
+ public void setIcon(Uri uri) {
+ if (mIconUri != null && mIconUri.equals(uri)) {
+ return;
+ }
+
+ mIconUri = uri;
+ if (mFetchBitmapTask != null) {
+ mFetchBitmapTask.cancel(true);
+ }
+ mFetchBitmapTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap == null) {
+ bitmap = BitmapFactory.decodeResource(getResources(),
+ R.drawable.album_art_placeholder);
+ }
+ setIcon(bitmap);
+ if (this == mFetchBitmapTask) {
+ mFetchBitmapTask = null;
+ }
+ }
+ };
+
+ mFetchBitmapTask.execute(uri);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mAutoSetup) {
+ mCastManager.addMiniController(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mFetchBitmapTask != null) {
+ mFetchBitmapTask.cancel(true);
+ mFetchBitmapTask = null;
+ }
+ if (mAutoSetup) {
+ mCastManager.removeMiniController(this);
+ }
+ }
+
+ @Override
+ public void setTitle(String title) {
+ mTitle.setText(title);
+ }
+
+ @Override
+ public void setSubtitle(String subtitle) {
+ mSubTitle.setText(subtitle);
+ }
+
+ @Override
+ public void setPlaybackStatus(int state, int idleReason) {
+ switch (state) {
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ mPlayPause.setVisibility(View.VISIBLE);
+ mPlayPause.setImageDrawable(getPauseStopDrawable());
+ setLoadingVisibility(false);
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ mPlayPause.setVisibility(View.VISIBLE);
+ mPlayPause.setImageDrawable(mPlayDrawable);
+ setLoadingVisibility(false);
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ switch (mStreamType) {
+ case MediaInfo.STREAM_TYPE_BUFFERED:
+ mPlayPause.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ break;
+ case MediaInfo.STREAM_TYPE_LIVE:
+ if (idleReason == MediaStatus.IDLE_REASON_CANCELED) {
+ mPlayPause.setVisibility(View.VISIBLE);
+ mPlayPause.setImageDrawable(mPlayDrawable);
+ setLoadingVisibility(false);
+ } else {
+ mPlayPause.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ }
+ break;
+ }
+ break;
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ mPlayPause.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(true);
+ break;
+ default:
+ mPlayPause.setVisibility(View.INVISIBLE);
+ setLoadingVisibility(false);
+ break;
+ }
+ }
+
+ @Override
+ public boolean isVisible() {
+ return isShown();
+ }
+
+ private void loadViews() {
+ mIcon = (ImageView) findViewById(R.id.icon_view);
+ mTitle = (TextView) findViewById(R.id.title_view);
+ mSubTitle = (TextView) findViewById(R.id.subtitle_view);
+ mPlayPause = (ImageView) findViewById(R.id.play_pause);
+ mLoading = (ProgressBar) findViewById(R.id.loading_view);
+ mMainContainer = findViewById(R.id.container_current);
+ mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
+ mUpcomingIcon = (ImageView) findViewById(R.id.icon_view_upcoming);
+ mUpcomingTitle = (TextView) findViewById(R.id.title_view_upcoming);
+ mUpcomingContainer = findViewById(R.id.container_upcoming);
+ mUpcomingPlay = findViewById(R.id.play_upcoming);
+ mUpcomingStop = findViewById(R.id.stop_upcoming);
+ }
+
+ private void setLoadingVisibility(boolean show) {
+ mLoading.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+
+ private Drawable getPauseStopDrawable() {
+ switch (mStreamType) {
+ case MediaInfo.STREAM_TYPE_BUFFERED:
+ return mPauseDrawable;
+ case MediaInfo.STREAM_TYPE_LIVE:
+ return mStopDrawable;
+ default:
+ return mPauseDrawable;
+ }
+ }
+
+ private void setUpcomingIcon(Uri uri) {
+ if (mUpcomingIconUri != null && mUpcomingIconUri.equals(uri)) {
+ return;
+ }
+
+ mUpcomingIconUri = uri;
+ if (mFetchUpcomingBitmapTask != null) {
+ mFetchUpcomingBitmapTask.cancel(true);
+ }
+ mFetchUpcomingBitmapTask = new FetchBitmapTask() {
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap == null) {
+ bitmap = BitmapFactory.decodeResource(getResources(),
+ R.drawable.album_art_placeholder);
+ }
+ setUpcomingIcon(bitmap);
+ if (this == mFetchUpcomingBitmapTask) {
+ mFetchUpcomingBitmapTask = null;
+ }
+ }
+ };
+
+ mFetchUpcomingBitmapTask.execute(uri);
+ }
+
+ private void setUpcomingTitle(String title) {
+ mUpcomingTitle.setText(title);
+ }
+
+ /**
+ * The interface for a listener that will be called when user interacts with the
+ * {@link MiniController}, like clicking on the play/pause button, etc.
+ */
+ public interface OnMiniControllerChangedListener extends OnFailedListener {
+
+ /**
+ * Notification that user has clicked on the Play/Pause button
+ *
+ * @throws TransientNetworkDisconnectionException
+ * @throws NoConnectionException
+ * @throws CastException
+ */
+ void onPlayPauseClicked(View v) throws CastException,
+ TransientNetworkDisconnectionException, NoConnectionException;
+
+ /**
+ * Notification that the user has clicked on the album art
+ *
+ * @throws NoConnectionException
+ * @throws TransientNetworkDisconnectionException
+ */
+ void onTargetActivityInvoked(Context context)
+ throws TransientNetworkDisconnectionException, NoConnectionException;
+
+ /**
+ * Called when the "play" button in the upcoming area is clicked.
+ */
+ void onUpcomingPlayClicked(View v, MediaQueueItem upcomingItem);
+
+ /**
+ * Called when the "stop" button in the upcoming area is clicked.
+ */
+ void onUpcomingStopClicked(View view, MediaQueueItem upcomingItem);
+
+ }
+}
diff --git a/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_democast.png b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_democast.png
new file mode 100644
index 000000000..ee4533bcf
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_notification.png b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_notification.png
new file mode 100644
index 000000000..72677ea3b
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-hdpi-v11/ic_stat_content_remove.png b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_content_remove.png
new file mode 100644
index 000000000..4f2511b6f
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi-v11/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/actionbar_bg.png b/CCL/src/main/res/drawable-hdpi/actionbar_bg.png
new file mode 100644
index 000000000..d44641816
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/actionbar_bg.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_close_sm_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_close_sm_dark.png
new file mode 100644
index 000000000..b4f9b1b52
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_close_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause.png
new file mode 100644
index 000000000..854a97fa3
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause_dark.png
new file mode 100644
index 000000000..90eeced0f
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause_light.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause_light.png
new file mode 100644
index 000000000..d0fecb25d
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause_light.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video.png
new file mode 100644
index 000000000..7f375e8a7
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video_large.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video_large.png
new file mode 100644
index 000000000..7644e7e98
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_pause_sm_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_pause_sm_dark.png
new file mode 100644
index 000000000..e5c4b7c88
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_pause_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play.png b/CCL/src/main/res/drawable-hdpi/ic_av_play.png
new file mode 100644
index 000000000..47266e7cd
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_play_dark.png
new file mode 100644
index 000000000..276680353
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play_light.png b/CCL/src/main/res/drawable-hdpi/ic_av_play_light.png
new file mode 100644
index 000000000..2e45a000d
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play_light.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video.png b/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video.png
new file mode 100644
index 000000000..f8f0de439
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video_large.png b/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video_large.png
new file mode 100644
index 000000000..e0b946494
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_play_sm_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_play_sm_dark.png
new file mode 100644
index 000000000..f3b709b50
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_play_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_stop.png b/CCL/src/main/res/drawable-hdpi/ic_av_stop.png
new file mode 100644
index 000000000..c8deb0ab5
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_stop.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_stop_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_stop_dark.png
new file mode 100644
index 000000000..b5dc4c986
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_stop_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_stop_light.png b/CCL/src/main/res/drawable-hdpi/ic_av_stop_light.png
new file mode 100644
index 000000000..9892ee3ff
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_stop_light.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_dark.png b/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_dark.png
new file mode 100644
index 000000000..b899558b9
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_light.png b/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_light.png
new file mode 100644
index 000000000..107531869
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_av_stop_sm_light.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_clear_black_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_clear_black_24dp.png
new file mode 100644
index 000000000..1a9cd75a0
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_clear_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_clear_black_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_clear_black_48dp.png
new file mode 100644
index 000000000..51b4401ca
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_clear_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_clear_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_clear_white_24dp.png
new file mode 100644
index 000000000..ceb1a1eeb
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_clear_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_clear_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_clear_white_48dp.png
new file mode 100644
index 000000000..6b717e0dd
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_clear_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_blue.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_blue.png
new file mode 100644
index 000000000..297bd8934
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_blue.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_24dp.png
new file mode 100644
index 000000000..599574545
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_36dp.png
new file mode 100644
index 000000000..b8db0fad4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_48dp.png
new file mode 100644
index 000000000..85fe17d5e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_googblue_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey.png
new file mode 100644
index 000000000..1828c6fac
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_24dp.png
new file mode 100644
index 000000000..dca62ab59
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_36dp.png
new file mode 100644
index 000000000..01f608450
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_48dp.png
new file mode 100644
index 000000000..324149f6d
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white.png
new file mode 100644
index 000000000..9b0c1cf9e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_24dp.png
new file mode 100644
index 000000000..b7acbad50
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_36dp.png
new file mode 100644
index 000000000..1a4e94f2f
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_48dp.png
new file mode 100644
index 000000000..514c4ea36
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_closed_caption_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_muted.png b/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_muted.png
new file mode 100644
index 000000000..d1caae052
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_muted.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_on.png b/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_on.png
new file mode 100644
index 000000000..ace4bbee8
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_device_access_volume_on.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_drag_updown_grey_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_drag_updown_grey_24dp.png
new file mode 100644
index 000000000..6a6f4e86e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_drag_updown_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_drag_updown_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_drag_updown_white_24dp.png
new file mode 100644
index 000000000..42564eccb
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_drag_updown_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_expand_list.png b/CCL/src/main/res/drawable-hdpi/ic_expand_list.png
new file mode 100644
index 000000000..7b8e11f03
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_expand_list.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_black_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_black_24dp.png
new file mode 100644
index 000000000..3539b4ef1
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_black_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_black_36dp.png
new file mode 100644
index 000000000..fb4967bcb
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_black_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_black_48dp.png
new file mode 100644
index 000000000..bb707eab9
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_64dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_64dp.png
new file mode 100644
index 000000000..37784162e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_80dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_80dp.png
new file mode 100644
index 000000000..cd04a64a8
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_64dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_64dp.png
new file mode 100644
index 000000000..1875f2093
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_80dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_80dp.png
new file mode 100644
index 000000000..cb0097291
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png
new file mode 100644
index 000000000..cfd97d5de
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png
new file mode 100644
index 000000000..dde9bb25c
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_48dp.png
new file mode 100644
index 000000000..aeb13ebc4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
new file mode 100644
index 000000000..4d2ea05c4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_pause_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_pause_white_48dp.png
new file mode 100644
index 000000000..7192ad487
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_pause_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 000000000..e9c288c99
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 000000000..5345ee3c4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_googblue_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_googblue_36dp.png
new file mode 100644
index 000000000..f0fd209b6
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png
new file mode 100644
index 000000000..9e9464e2c
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png
new file mode 100644
index 000000000..cd0e433d5
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_48dp.png
new file mode 100644
index 000000000..06485234a
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 000000000..57c9fa546
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 000000000..29adeed05
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png
new file mode 100644
index 000000000..547ef30aa
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_64dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_64dp.png
new file mode 100644
index 000000000..7776fdb71
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_80dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_80dp.png
new file mode 100644
index 000000000..b4ff088eb
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_64dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_64dp.png
new file mode 100644
index 000000000..7450b95a8
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_80dp.png b/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_80dp.png
new file mode 100644
index 000000000..035c0c046
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_play_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_playlist_black_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_playlist_black_24dp.png
new file mode 100644
index 000000000..6f7099b6e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_playlist_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_playlist_grey_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_playlist_grey_24dp.png
new file mode 100644
index 000000000..3e3c4f7f4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_playlist_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_playlist_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_playlist_white_24dp.png
new file mode 100644
index 000000000..798328c6d
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_playlist_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_remove_circle_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_remove_circle_white_24dp.png
new file mode 100644
index 000000000..284d71309
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_remove_circle_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_repeat_one_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_repeat_one_grey600_36dp.png
new file mode 100644
index 000000000..b0f359095
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_repeat_one_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_repeat_one_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_repeat_one_white_36dp.png
new file mode 100644
index 000000000..689e8ffd8
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_repeat_one_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_repeat_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_repeat_white_36dp.png
new file mode 100644
index 000000000..8dbcc999a
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_repeat_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_shuffle_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_shuffle_grey600_36dp.png
new file mode 100644
index 000000000..af61e8384
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_shuffle_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_shuffle_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_shuffle_white_36dp.png
new file mode 100644
index 000000000..12bf72b25
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_shuffle_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey300_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey300_48dp.png
new file mode 100644
index 000000000..c9abc7970
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey600_48dp.png
new file mode 100644
index 000000000..21bce8b88
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_next_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_36dp.png
new file mode 100644
index 000000000..9cc4bf9f3
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_48dp.png
new file mode 100644
index 000000000..3ee6d756e
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_next_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey300_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey300_48dp.png
new file mode 100644
index 000000000..cf068693c
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey600_48dp.png
new file mode 100644
index 000000000..4ae7dd589
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_36dp.png
new file mode 100644
index 000000000..da1c1c958
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_48dp.png
new file mode 100644
index 000000000..1181ec926
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_skip_previous_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stat_action_democast.png b/CCL/src/main/res/drawable-hdpi/ic_stat_action_democast.png
new file mode 100644
index 000000000..95ee5e511
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stat_action_notification.png b/CCL/src/main/res/drawable-hdpi/ic_stat_action_notification.png
new file mode 100644
index 000000000..32266acc0
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stat_content_remove.png b/CCL/src/main/res/drawable-hdpi/ic_stat_content_remove.png
new file mode 100644
index 000000000..29fdf59d3
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_black_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_black_36dp.png
new file mode 100644
index 000000000..f41a0f198
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_circle_white_80dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_circle_white_80dp.png
new file mode 100644
index 000000000..e2e04739a
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_24dp.png
new file mode 100644
index 000000000..f2ac9b2e4
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_36dp.png
new file mode 100644
index 000000000..ff4a2bdaa
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_48dp.png
new file mode 100644
index 000000000..42773b37d
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_white_18dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_white_18dp.png
new file mode 100644
index 000000000..3bacfbcd6
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_white_18dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_white_24dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_white_24dp.png
new file mode 100644
index 000000000..dfff26cee
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_white_36dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_white_36dp.png
new file mode 100644
index 000000000..266214dd9
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/ic_stop_white_48dp.png b/CCL/src/main/res/drawable-hdpi/ic_stop_white_48dp.png
new file mode 100644
index 000000000..801d34111
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/ic_stop_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/mini_bg.png b/CCL/src/main/res/drawable-hdpi/mini_bg.png
new file mode 100644
index 000000000..d12a7b1c1
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/mini_bg.png differ
diff --git a/CCL/src/main/res/drawable-hdpi/mini_bg_shadow.png b/CCL/src/main/res/drawable-hdpi/mini_bg_shadow.png
new file mode 100644
index 000000000..78b505e1a
Binary files /dev/null and b/CCL/src/main/res/drawable-hdpi/mini_bg_shadow.png differ
diff --git a/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_democast.png b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_democast.png
new file mode 100644
index 000000000..1d775f99a
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_notification.png b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_notification.png
new file mode 100644
index 000000000..fdf76add2
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-mdpi-v11/ic_stat_content_remove.png b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_content_remove.png
new file mode 100644
index 000000000..4a94cf4cc
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi-v11/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_close_sm_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_close_sm_dark.png
new file mode 100644
index 000000000..99d28ac00
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_close_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause.png
new file mode 100644
index 000000000..27399677f
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause_dark.png
new file mode 100644
index 000000000..c443134f3
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause_light.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause_light.png
new file mode 100644
index 000000000..60e49d26e
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause_light.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video.png
new file mode 100644
index 000000000..0a98ddda8
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video_large.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video_large.png
new file mode 100644
index 000000000..570297ec8
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_pause_sm_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_pause_sm_dark.png
new file mode 100644
index 000000000..bd0f4b04d
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_pause_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play.png b/CCL/src/main/res/drawable-mdpi/ic_av_play.png
new file mode 100644
index 000000000..944304ad3
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_play_dark.png
new file mode 100644
index 000000000..4c3a9a6f7
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play_light.png b/CCL/src/main/res/drawable-mdpi/ic_av_play_light.png
new file mode 100644
index 000000000..4c447195b
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play_light.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video.png b/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video.png
new file mode 100644
index 000000000..2ffac8158
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video_large.png b/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video_large.png
new file mode 100644
index 000000000..d7b329684
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_play_sm_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_play_sm_dark.png
new file mode 100644
index 000000000..856562b97
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_play_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_stop.png b/CCL/src/main/res/drawable-mdpi/ic_av_stop.png
new file mode 100644
index 000000000..cc3e9dfc9
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_stop.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_stop_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_stop_dark.png
new file mode 100644
index 000000000..96aad2400
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_stop_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_stop_light.png b/CCL/src/main/res/drawable-mdpi/ic_av_stop_light.png
new file mode 100644
index 000000000..2e10b4497
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_stop_light.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_dark.png b/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_dark.png
new file mode 100644
index 000000000..96c6458cd
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_light.png b/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_light.png
new file mode 100644
index 000000000..4a8456866
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_av_stop_sm_light.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_muted.png b/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_muted.png
new file mode 100644
index 000000000..f37e45370
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_muted.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_on.png b/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_on.png
new file mode 100644
index 000000000..0a20d8a6e
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_device_access_volume_on.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_pause_black_36dp.png b/CCL/src/main/res/drawable-mdpi/ic_pause_black_36dp.png
new file mode 100644
index 000000000..4dffa8e53
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_pause_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_playlist_black_24dp.png b/CCL/src/main/res/drawable-mdpi/ic_playlist_black_24dp.png
new file mode 100644
index 000000000..bde62ae01
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_playlist_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_playlist_grey_24dp.png b/CCL/src/main/res/drawable-mdpi/ic_playlist_grey_24dp.png
new file mode 100644
index 000000000..1b2f1d04f
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_playlist_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_playlist_white_24dp.png b/CCL/src/main/res/drawable-mdpi/ic_playlist_white_24dp.png
new file mode 100644
index 000000000..8cb381810
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_playlist_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_stat_action_democast.png b/CCL/src/main/res/drawable-mdpi/ic_stat_action_democast.png
new file mode 100644
index 000000000..09843dc59
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_stat_action_notification.png b/CCL/src/main/res/drawable-mdpi/ic_stat_action_notification.png
new file mode 100644
index 000000000..3aca0f3fd
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/ic_stat_content_remove.png b/CCL/src/main/res/drawable-mdpi/ic_stat_content_remove.png
new file mode 100644
index 000000000..5177ed6ac
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/mini_bg.png b/CCL/src/main/res/drawable-mdpi/mini_bg.png
new file mode 100644
index 000000000..c3e500481
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/mini_bg.png differ
diff --git a/CCL/src/main/res/drawable-mdpi/mini_bg_shadow.png b/CCL/src/main/res/drawable-mdpi/mini_bg_shadow.png
new file mode 100644
index 000000000..47963bab6
Binary files /dev/null and b/CCL/src/main/res/drawable-mdpi/mini_bg_shadow.png differ
diff --git a/CCL/src/main/res/drawable-nodpi/album_art_placeholder.png b/CCL/src/main/res/drawable-nodpi/album_art_placeholder.png
new file mode 100644
index 000000000..955c7b3de
Binary files /dev/null and b/CCL/src/main/res/drawable-nodpi/album_art_placeholder.png differ
diff --git a/CCL/src/main/res/drawable-nodpi/album_art_placeholder_large.png b/CCL/src/main/res/drawable-nodpi/album_art_placeholder_large.png
new file mode 100644
index 000000000..c1be2bb8f
Binary files /dev/null and b/CCL/src/main/res/drawable-nodpi/album_art_placeholder_large.png differ
diff --git a/CCL/src/main/res/drawable-nodpi/mini_controller_img_placeholder.png b/CCL/src/main/res/drawable-nodpi/mini_controller_img_placeholder.png
new file mode 100644
index 000000000..1ea1f1ef6
Binary files /dev/null and b/CCL/src/main/res/drawable-nodpi/mini_controller_img_placeholder.png differ
diff --git a/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_play.xml b/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_play.xml
new file mode 100644
index 000000000..bacd9ba8c
--- /dev/null
+++ b/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_play.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_stop.xml b/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_stop.xml
new file mode 100644
index 000000000..161148136
--- /dev/null
+++ b/CCL/src/main/res/drawable-sw600dp/ic_mini_controller_upcoming_stop.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_democast.png b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_democast.png
new file mode 100644
index 000000000..ff1b1cb5a
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_notification.png b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_notification.png
new file mode 100644
index 000000000..c654d6b44
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_content_remove.png b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_content_remove.png
new file mode 100644
index 000000000..318b7154a
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi-v11/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/default_video.png b/CCL/src/main/res/drawable-xhdpi/default_video.png
new file mode 100644
index 000000000..20343b715
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/default_video.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_close_sm_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_close_sm_dark.png
new file mode 100644
index 000000000..b42224fad
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_close_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause.png
new file mode 100644
index 000000000..d66d00a6e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_dark.png
new file mode 100644
index 000000000..2a2880d81
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause_light.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_light.png
new file mode 100644
index 000000000..9e9c0e78c
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_light.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video.png
new file mode 100644
index 000000000..2947c6c9e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video_large.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video_large.png
new file mode 100644
index 000000000..dfff5a82c
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_pause_sm_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_sm_dark.png
new file mode 100644
index 000000000..84b27911a
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_pause_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play.png
new file mode 100644
index 000000000..17321c4f2
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play_dark.png
new file mode 100644
index 000000000..a6bcb4b40
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play_light.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play_light.png
new file mode 100644
index 000000000..0a0c82f76
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play_light.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video.png
new file mode 100644
index 000000000..e35141694
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video_large.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video_large.png
new file mode 100644
index 000000000..a36b6e812
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_play_sm_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_play_sm_dark.png
new file mode 100644
index 000000000..c8d1472c0
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_play_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_stop.png b/CCL/src/main/res/drawable-xhdpi/ic_av_stop.png
new file mode 100644
index 000000000..e48c4e52f
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_stop.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_stop_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_dark.png
new file mode 100644
index 000000000..ef3e3212a
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_stop_light.png b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_light.png
new file mode 100644
index 000000000..7c1abffd5
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_light.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_dark.png b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_dark.png
new file mode 100644
index 000000000..d7ea0481e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_light.png b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_light.png
new file mode 100644
index 000000000..673dd6525
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_av_stop_sm_light.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png
new file mode 100644
index 000000000..6bc437298
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_clear_black_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_clear_black_48dp.png
new file mode 100644
index 000000000..df42feecb
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_clear_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png
new file mode 100644
index 000000000..b7c7ffd0e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_clear_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_clear_white_48dp.png
new file mode 100644
index 000000000..396419219
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_clear_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_blue.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_blue.png
new file mode 100644
index 000000000..2c69a4b3c
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_blue.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_24dp.png
new file mode 100644
index 000000000..3d28118f5
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_36dp.png
new file mode 100644
index 000000000..85fe17d5e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_48dp.png
new file mode 100644
index 000000000..ed48fb159
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_googblue_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey.png
new file mode 100644
index 000000000..ea0c36392
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_24dp.png
new file mode 100644
index 000000000..ee5f62d69
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_36dp.png
new file mode 100644
index 000000000..324149f6d
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_48dp.png
new file mode 100644
index 000000000..5108c46c3
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white.png
new file mode 100644
index 000000000..f3743f285
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_24dp.png
new file mode 100644
index 000000000..364d6ee0d
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_36dp.png
new file mode 100644
index 000000000..514c4ea36
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_48dp.png
new file mode 100644
index 000000000..12d280c12
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_closed_caption_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_muted.png b/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_muted.png
new file mode 100644
index 000000000..027a4cb0c
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_muted.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_on.png b/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_on.png
new file mode 100644
index 000000000..0f95ee74d
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_device_access_volume_on.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_grey_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_grey_24dp.png
new file mode 100644
index 000000000..12a30f296
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_white_24dp.png
new file mode 100644
index 000000000..51211e500
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_drag_updown_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_expand_list.png b/CCL/src/main/res/drawable-xhdpi/ic_expand_list.png
new file mode 100644
index 000000000..2ae6240de
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_expand_list.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_black_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_24dp.png
new file mode 100644
index 000000000..74068eae0
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png
new file mode 100644
index 000000000..ec6617a79
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_black_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_48dp.png
new file mode 100644
index 000000000..792104ff3
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_64dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_64dp.png
new file mode 100644
index 000000000..f6d0ecbf1
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_80dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_80dp.png
new file mode 100644
index 000000000..28c31cb3b
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_64dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_64dp.png
new file mode 100644
index 000000000..99323e404
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_80dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_80dp.png
new file mode 100644
index 000000000..5503d4b3f
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png
new file mode 100644
index 000000000..6708b4161
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png
new file mode 100644
index 000000000..aeb13ebc4
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_48dp.png
new file mode 100644
index 000000000..239b5a869
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
new file mode 100644
index 000000000..f49aed757
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_pause_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_pause_white_48dp.png
new file mode 100644
index 000000000..660ac6585
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_pause_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 000000000..f208795fc
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 000000000..d12d49562
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_googblue_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_googblue_36dp.png
new file mode 100644
index 000000000..462620bed
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png
new file mode 100644
index 000000000..8f6a72598
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png
new file mode 100644
index 000000000..06485234a
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_48dp.png
new file mode 100644
index 000000000..4be0ef363
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 000000000..a3c80e73d
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 000000000..547ef30aa
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png
new file mode 100644
index 000000000..be5c062b5
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_64dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_64dp.png
new file mode 100644
index 000000000..b16f0a0df
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_80dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_80dp.png
new file mode 100644
index 000000000..237c56bad
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_64dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_64dp.png
new file mode 100644
index 000000000..b870d64bc
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_80dp.png b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_80dp.png
new file mode 100644
index 000000000..d48061e68
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_play_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_playlist_black_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_playlist_black_24dp.png
new file mode 100644
index 000000000..22405eb02
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_playlist_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_playlist_grey_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_playlist_grey_24dp.png
new file mode 100644
index 000000000..73c9c69aa
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_playlist_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_playlist_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_playlist_white_24dp.png
new file mode 100644
index 000000000..45776a8bc
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_playlist_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_remove_circle_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_remove_circle_white_24dp.png
new file mode 100644
index 000000000..908b9ec4e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_remove_circle_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_repeat_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_repeat_grey600_36dp.png
new file mode 100644
index 000000000..3b097663f
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_repeat_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_grey600_36dp.png
new file mode 100644
index 000000000..b1d59fac4
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_white_36dp.png
new file mode 100644
index 000000000..60dc326cc
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_repeat_one_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_repeat_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_repeat_white_36dp.png
new file mode 100644
index 000000000..418d3e9e0
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_repeat_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_shuffle_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_shuffle_grey600_36dp.png
new file mode 100644
index 000000000..f710be951
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_shuffle_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_shuffle_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_shuffle_white_36dp.png
new file mode 100644
index 000000000..dc8e5341b
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_shuffle_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey300_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey300_48dp.png
new file mode 100644
index 000000000..2b142941e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey600_48dp.png
new file mode 100644
index 000000000..65d83efdd
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_36dp.png
new file mode 100644
index 000000000..3ee6d756e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_48dp.png
new file mode 100644
index 000000000..19c4929cc
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_next_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey300_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey300_48dp.png
new file mode 100644
index 000000000..df38227b5
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey600_48dp.png
new file mode 100644
index 000000000..480328269
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_36dp.png
new file mode 100644
index 000000000..1181ec926
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_48dp.png
new file mode 100644
index 000000000..f9186c0b6
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_skip_previous_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stat_action_democast.png b/CCL/src/main/res/drawable-xhdpi/ic_stat_action_democast.png
new file mode 100644
index 000000000..5fe191255
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stat_action_notification.png b/CCL/src/main/res/drawable-xhdpi/ic_stat_action_notification.png
new file mode 100644
index 000000000..bb0ae81df
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stat_content_remove.png b/CCL/src/main/res/drawable-xhdpi/ic_stat_content_remove.png
new file mode 100644
index 000000000..40961e4cf
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_black_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_black_36dp.png
new file mode 100644
index 000000000..9d6b65da7
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_circle_white_80dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_circle_white_80dp.png
new file mode 100644
index 000000000..ab7dd3cae
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_24dp.png
new file mode 100644
index 000000000..8d71c4403
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_36dp.png
new file mode 100644
index 000000000..42773b37d
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_48dp.png
new file mode 100644
index 000000000..772600924
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_white_18dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_18dp.png
new file mode 100644
index 000000000..dfff26cee
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_18dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png
new file mode 100644
index 000000000..3ad2c9c4e
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_white_36dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_36dp.png
new file mode 100644
index 000000000..801d34111
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/ic_stop_white_48dp.png b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_48dp.png
new file mode 100644
index 000000000..523933667
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/ic_stop_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/mini_bg.png b/CCL/src/main/res/drawable-xhdpi/mini_bg.png
new file mode 100644
index 000000000..40b751f90
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/mini_bg.png differ
diff --git a/CCL/src/main/res/drawable-xhdpi/mini_bg_shadow.png b/CCL/src/main/res/drawable-xhdpi/mini_bg_shadow.png
new file mode 100644
index 000000000..cc4f61df6
Binary files /dev/null and b/CCL/src/main/res/drawable-xhdpi/mini_bg_shadow.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_democast.png b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_democast.png
new file mode 100644
index 000000000..a6b25c885
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_notification.png b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_notification.png
new file mode 100644
index 000000000..4a434826b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_content_remove.png b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_content_remove.png
new file mode 100644
index 000000000..e60e87db3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi-v11/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_close_sm_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_close_sm_dark.png
new file mode 100644
index 000000000..e58e25f9f
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_close_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause.png
new file mode 100644
index 000000000..f7eaf517b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_dark.png
new file mode 100644
index 000000000..f782aa887
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_light.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_light.png
new file mode 100644
index 000000000..a4af4ee5c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_light.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video.png
new file mode 100644
index 000000000..58ef402f8
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video_large.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video_large.png
new file mode 100644
index 000000000..cc787ce83
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_sm_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_sm_dark.png
new file mode 100644
index 000000000..bd03035c1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_pause_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play.png
new file mode 100644
index 000000000..6c2b02189
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_dark.png
new file mode 100644
index 000000000..45f86da78
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play_light.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_light.png
new file mode 100644
index 000000000..7f9d25293
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_light.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video.png
new file mode 100644
index 000000000..eebe36262
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video_large.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video_large.png
new file mode 100644
index 000000000..65ca82d66
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_over_video_large.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_play_sm_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_sm_dark.png
new file mode 100644
index 000000000..97c2614d1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_play_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_stop.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop.png
new file mode 100644
index 000000000..ea57edf8d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_dark.png
new file mode 100644
index 000000000..a637486ec
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_light.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_light.png
new file mode 100644
index 000000000..6edcaa548
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_light.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_dark.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_dark.png
new file mode 100644
index 000000000..0a0dc3350
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_dark.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_light.png b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_light.png
new file mode 100644
index 000000000..3e54758b4
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_av_stop_sm_light.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png
new file mode 100644
index 000000000..51b4401ca
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_48dp.png
new file mode 100644
index 000000000..e2ee25f60
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_clear_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png
new file mode 100644
index 000000000..6b717e0dd
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_48dp.png
new file mode 100644
index 000000000..4927bc242
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_clear_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_blue.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_blue.png
new file mode 100644
index 000000000..2a770be52
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_blue.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_24dp.png
new file mode 100644
index 000000000..85fe17d5e
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_36dp.png
new file mode 100644
index 000000000..4345d025c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_48dp.png
new file mode 100644
index 000000000..2bccf7f3a
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_googblue_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey.png
new file mode 100644
index 000000000..02e68b290
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_24dp.png
new file mode 100644
index 000000000..324149f6d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_36dp.png
new file mode 100644
index 000000000..774e205aa
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_48dp.png
new file mode 100644
index 000000000..faa153c9a
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white.png
new file mode 100644
index 000000000..691300cfb
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_24dp.png
new file mode 100644
index 000000000..514c4ea36
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_36dp.png
new file mode 100644
index 000000000..71918a5ac
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_48dp.png
new file mode 100644
index 000000000..c78177570
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_closed_caption_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_muted.png b/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_muted.png
new file mode 100644
index 000000000..74518d941
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_muted.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_on.png b/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_on.png
new file mode 100644
index 000000000..8704f2b61
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_device_access_volume_on.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_grey_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_grey_24dp.png
new file mode 100644
index 000000000..5213d3646
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_white_24dp.png
new file mode 100644
index 000000000..637f829d7
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_drag_updown_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_24dp.png
new file mode 100644
index 000000000..bb707eab9
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png
new file mode 100644
index 000000000..a691659a7
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_48dp.png
new file mode 100644
index 000000000..dc63538f3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_64dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_64dp.png
new file mode 100644
index 000000000..28c31cb3b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_80dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_80dp.png
new file mode 100644
index 000000000..eb67581f2
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_64dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_64dp.png
new file mode 100644
index 000000000..5503d4b3f
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_80dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_80dp.png
new file mode 100644
index 000000000..dd0342bd1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png
new file mode 100644
index 000000000..aeb13ebc4
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png
new file mode 100644
index 000000000..0f4b4ed73
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_48dp.png
new file mode 100644
index 000000000..78456c7cd
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 000000000..7192ad487
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_48dp.png
new file mode 100644
index 000000000..3ea7e03e5
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_pause_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 000000000..5345ee3c4
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 000000000..1c57756b0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_googblue_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_googblue_36dp.png
new file mode 100644
index 000000000..b4ee49971
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png
new file mode 100644
index 000000000..06485234a
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png
new file mode 100644
index 000000000..aa60f481b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_48dp.png
new file mode 100644
index 000000000..27a0dc0a8
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 000000000..547ef30aa
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 000000000..23bb1ba9f
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png
new file mode 100644
index 000000000..2745c3ab9
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_64dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_64dp.png
new file mode 100644
index 000000000..237c56bad
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_80dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_80dp.png
new file mode 100644
index 000000000..a744b17ae
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_64dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_64dp.png
new file mode 100644
index 000000000..d48061e68
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_80dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_80dp.png
new file mode 100644
index 000000000..368f577e3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_play_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_playlist_black_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_black_24dp.png
new file mode 100644
index 000000000..29b8fcfac
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_playlist_grey_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_grey_24dp.png
new file mode 100644
index 000000000..1a6550f6d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_playlist_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_white_24dp.png
new file mode 100644
index 000000000..424196689
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_playlist_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_remove_circle_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_remove_circle_white_24dp.png
new file mode 100644
index 000000000..5079102d2
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_remove_circle_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_repeat_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_grey600_36dp.png
new file mode 100644
index 000000000..8b9fa778b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_repeat_one_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_one_white_36dp.png
new file mode 100644
index 000000000..0303bc104
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_one_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_repeat_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_white_36dp.png
new file mode 100644
index 000000000..68a633ca8
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_repeat_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_grey600_36dp.png
new file mode 100644
index 000000000..de53ec550
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_white_36dp.png
new file mode 100644
index 000000000..fad125952
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_shuffle_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey300_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey300_48dp.png
new file mode 100644
index 000000000..283d81821
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey600_48dp.png
new file mode 100644
index 000000000..346533ff1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_36dp.png
new file mode 100644
index 000000000..4652215cc
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_48dp.png
new file mode 100644
index 000000000..292811616
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_next_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey300_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey300_48dp.png
new file mode 100644
index 000000000..6209a7cd0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey600_48dp.png
new file mode 100644
index 000000000..8509af916
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png
new file mode 100644
index 000000000..d66306419
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_48dp.png
new file mode 100644
index 000000000..73e30477c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_skip_previous_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_democast.png b/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_democast.png
new file mode 100644
index 000000000..4917f1ca5
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_democast.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_notification.png b/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_notification.png
new file mode 100644
index 000000000..9265e14d0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stat_action_notification.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stat_content_remove.png b/CCL/src/main/res/drawable-xxhdpi/ic_stat_content_remove.png
new file mode 100644
index 000000000..59c4b14a2
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stat_content_remove.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_black_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_black_36dp.png
new file mode 100644
index 000000000..75f47c1bf
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_circle_white_80dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_circle_white_80dp.png
new file mode 100644
index 000000000..d2a6d4f7d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_24dp.png
new file mode 100644
index 000000000..42773b37d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_36dp.png
new file mode 100644
index 000000000..78ab5ff97
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_48dp.png
new file mode 100644
index 000000000..5ed9f7ec0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_18dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_18dp.png
new file mode 100644
index 000000000..266214dd9
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_18dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png
new file mode 100644
index 000000000..801d34111
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_36dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_36dp.png
new file mode 100644
index 000000000..adef631a0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_48dp.png b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_48dp.png
new file mode 100644
index 000000000..035ca181c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/ic_stop_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/mini_bg.png b/CCL/src/main/res/drawable-xxhdpi/mini_bg.png
new file mode 100644
index 000000000..b3bfebd11
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/mini_bg.png differ
diff --git a/CCL/src/main/res/drawable-xxhdpi/mini_bg_shadow.png b/CCL/src/main/res/drawable-xxhdpi/mini_bg_shadow.png
new file mode 100644
index 000000000..42c53aef7
Binary files /dev/null and b/CCL/src/main/res/drawable-xxhdpi/mini_bg_shadow.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_24dp.png
new file mode 100644
index 000000000..ed48fb159
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_36dp.png
new file mode 100644
index 000000000..2bccf7f3a
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_48dp.png
new file mode 100644
index 000000000..ef8ecfa2c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_googblue_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_24dp.png
new file mode 100644
index 000000000..5108c46c3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_36dp.png
new file mode 100644
index 000000000..faa153c9a
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_48dp.png
new file mode 100644
index 000000000..f9b474193
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_24dp.png
new file mode 100644
index 000000000..12d280c12
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_36dp.png
new file mode 100644
index 000000000..c78177570
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_48dp.png
new file mode 100644
index 000000000..e771a1991
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_closed_caption_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_grey_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_grey_24dp.png
new file mode 100644
index 000000000..7d9a7e5c3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_white_24dp.png
new file mode 100644
index 000000000..05cfeb2ff
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_drag_updown_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_24dp.png
new file mode 100644
index 000000000..792104ff3
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png
new file mode 100644
index 000000000..b492a6988
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_48dp.png
new file mode 100644
index 000000000..66178aad1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_64dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_64dp.png
new file mode 100644
index 000000000..48ee94f83
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_80dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_80dp.png
new file mode 100644
index 000000000..f26760e47
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_64dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_64dp.png
new file mode 100644
index 000000000..d71bd4242
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_80dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_80dp.png
new file mode 100644
index 000000000..60ab51cbf
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_24dp.png
new file mode 100644
index 000000000..239b5a869
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_36dp.png
new file mode 100644
index 000000000..78456c7cd
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_48dp.png
new file mode 100644
index 000000000..9d5106865
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png
new file mode 100644
index 000000000..660ac6585
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png
new file mode 100644
index 000000000..76482b1fd
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 000000000..d12d49562
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png
new file mode 100644
index 000000000..1c57756b0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png
new file mode 100644
index 000000000..904bbdbb0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_googblue_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_googblue_36dp.png
new file mode 100644
index 000000000..1d514a7f8
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_googblue_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_24dp.png
new file mode 100644
index 000000000..4be0ef363
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_36dp.png
new file mode 100644
index 000000000..27a0dc0a8
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_48dp.png
new file mode 100644
index 000000000..f17508827
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 000000000..be5c062b5
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png
new file mode 100644
index 000000000..2745c3ab9
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png
new file mode 100644
index 000000000..8dbc4ea7c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_64dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_64dp.png
new file mode 100644
index 000000000..fc3a77b8b
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_80dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_80dp.png
new file mode 100644
index 000000000..864b6f61d
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_grey_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_64dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_64dp.png
new file mode 100644
index 000000000..4f612f966
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_64dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_80dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_80dp.png
new file mode 100644
index 000000000..6e0070e37
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_play_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_black_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_black_24dp.png
new file mode 100644
index 000000000..93c75058f
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_black_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_grey_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_grey_24dp.png
new file mode 100644
index 000000000..84e3750a9
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_grey_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_white_24dp.png
new file mode 100644
index 000000000..59e93c814
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_playlist_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_remove_circle_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_remove_circle_white_24dp.png
new file mode 100644
index 000000000..0108c89e6
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_remove_circle_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_grey600_36dp.png
new file mode 100644
index 000000000..20993de91
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_36dp.png
new file mode 100644
index 000000000..596b55266
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_white_36dp.png
new file mode 100644
index 000000000..bf7607966
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_repeat_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_36dp.png
new file mode 100644
index 000000000..423984530
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_white_36dp.png
new file mode 100644
index 000000000..540efc14f
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_shuffle_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey300_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey300_48dp.png
new file mode 100644
index 000000000..910da2b32
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey600_48dp.png
new file mode 100644
index 000000000..08b2167d1
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png
new file mode 100644
index 000000000..292811616
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png
new file mode 100644
index 000000000..5524dd776
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey300_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey300_48dp.png
new file mode 100644
index 000000000..3f0cd2838
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey300_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey600_48dp.png
new file mode 100644
index 000000000..7c2e012ab
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png
new file mode 100644
index 000000000..73e30477c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png
new file mode 100644
index 000000000..9ecac1657
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_black_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_black_36dp.png
new file mode 100644
index 000000000..eac183db7
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_black_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_circle_white_80dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_circle_white_80dp.png
new file mode 100644
index 000000000..fb784ac2e
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_circle_white_80dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_24dp.png
new file mode 100644
index 000000000..772600924
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_36dp.png
new file mode 100644
index 000000000..5ed9f7ec0
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_48dp.png
new file mode 100644
index 000000000..337f92980
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_grey600_48dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_18dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_18dp.png
new file mode 100644
index 000000000..801d34111
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_18dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png
new file mode 100644
index 000000000..523933667
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_24dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_36dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_36dp.png
new file mode 100644
index 000000000..035ca181c
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_36dp.png differ
diff --git a/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_48dp.png b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_48dp.png
new file mode 100644
index 000000000..7221e0313
Binary files /dev/null and b/CCL/src/main/res/drawable-xxxhdpi/ic_stop_white_48dp.png differ
diff --git a/CCL/src/main/res/drawable/actionbar_bg_gradient_light.xml b/CCL/src/main/res/drawable/actionbar_bg_gradient_light.xml
new file mode 100644
index 000000000..a83da822e
--- /dev/null
+++ b/CCL/src/main/res/drawable/actionbar_bg_gradient_light.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/CCL/src/main/res/drawable/cast_player_bg_gradient_light.xml b/CCL/src/main/res/drawable/cast_player_bg_gradient_light.xml
new file mode 100644
index 000000000..054cecf7c
--- /dev/null
+++ b/CCL/src/main/res/drawable/cast_player_bg_gradient_light.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/cc.xml b/CCL/src/main/res/drawable/cc.xml
new file mode 100644
index 000000000..de5c9d7a4
--- /dev/null
+++ b/CCL/src/main/res/drawable/cc.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_media_route_controller_pause.xml b/CCL/src/main/res/drawable/ic_media_route_controller_pause.xml
new file mode 100644
index 000000000..9bed499a7
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_media_route_controller_pause.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_media_route_controller_play.xml b/CCL/src/main/res/drawable/ic_media_route_controller_play.xml
new file mode 100644
index 000000000..84b1a2ffa
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_media_route_controller_play.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_media_route_controller_stop.xml b/CCL/src/main/res/drawable/ic_media_route_controller_stop.xml
new file mode 100644
index 000000000..fa172ec87
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_media_route_controller_stop.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_mini_controller_pause.xml b/CCL/src/main/res/drawable/ic_mini_controller_pause.xml
new file mode 100644
index 000000000..8cf3c28cb
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_mini_controller_pause.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_mini_controller_play.xml b/CCL/src/main/res/drawable/ic_mini_controller_play.xml
new file mode 100644
index 000000000..ca367f8a2
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_mini_controller_play.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_mini_controller_stop.xml b/CCL/src/main/res/drawable/ic_mini_controller_stop.xml
new file mode 100644
index 000000000..562f43486
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_mini_controller_stop.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_mini_controller_upcoming_play.xml b/CCL/src/main/res/drawable/ic_mini_controller_upcoming_play.xml
new file mode 100644
index 000000000..07e29e5d3
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_mini_controller_upcoming_play.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_mini_controller_upcoming_stop.xml b/CCL/src/main/res/drawable/ic_mini_controller_upcoming_stop.xml
new file mode 100644
index 000000000..f7e05d6d6
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_mini_controller_upcoming_stop.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_disconnect_24dp.xml b/CCL/src/main/res/drawable/ic_notification_disconnect_24dp.xml
new file mode 100644
index 000000000..ca04a48cd
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_disconnect_24dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_pause_24dp.xml b/CCL/src/main/res/drawable/ic_notification_pause_24dp.xml
new file mode 100644
index 000000000..e1ab3b19c
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_pause_24dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_pause_48dp.xml b/CCL/src/main/res/drawable/ic_notification_pause_48dp.xml
new file mode 100644
index 000000000..05b59a611
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_pause_48dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_play_24dp.xml b/CCL/src/main/res/drawable/ic_notification_play_24dp.xml
new file mode 100644
index 000000000..bea2c5af0
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_play_24dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_play_48dp.xml b/CCL/src/main/res/drawable/ic_notification_play_48dp.xml
new file mode 100644
index 000000000..715fdaf3a
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_play_48dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_stop_24dp.xml b/CCL/src/main/res/drawable/ic_notification_stop_24dp.xml
new file mode 100644
index 000000000..860a90c46
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_stop_24dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/ic_notification_stop_48dp.xml b/CCL/src/main/res/drawable/ic_notification_stop_48dp.xml
new file mode 100644
index 000000000..338f5b7fb
--- /dev/null
+++ b/CCL/src/main/res/drawable/ic_notification_stop_48dp.xml
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/mini_gradient_light.xml b/CCL/src/main/res/drawable/mini_gradient_light.xml
new file mode 100644
index 000000000..8e685d959
--- /dev/null
+++ b/CCL/src/main/res/drawable/mini_gradient_light.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/CCL/src/main/res/drawable/progress_drawable.xml b/CCL/src/main/res/drawable/progress_drawable.xml
new file mode 100644
index 000000000..a7c40ed11
--- /dev/null
+++ b/CCL/src/main/res/drawable/progress_drawable.xml
@@ -0,0 +1,35 @@
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/skip_next_button.xml b/CCL/src/main/res/drawable/skip_next_button.xml
new file mode 100644
index 000000000..b7026fc0d
--- /dev/null
+++ b/CCL/src/main/res/drawable/skip_next_button.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/drawable/skip_previous_button.xml b/CCL/src/main/res/drawable/skip_previous_button.xml
new file mode 100644
index 000000000..8624acf8a
--- /dev/null
+++ b/CCL/src/main/res/drawable/skip_previous_button.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout-land/cast_activity.xml b/CCL/src/main/res/layout-land/cast_activity.xml
new file mode 100644
index 000000000..8bc966223
--- /dev/null
+++ b/CCL/src/main/res/layout-land/cast_activity.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CCL/src/main/res/layout-v10/custom_notification.xml b/CCL/src/main/res/layout-v10/custom_notification.xml
new file mode 100644
index 000000000..e238f44ed
--- /dev/null
+++ b/CCL/src/main/res/layout-v10/custom_notification.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout-v14/custom_notification.xml b/CCL/src/main/res/layout-v14/custom_notification.xml
new file mode 100755
index 000000000..4bb7d42e2
--- /dev/null
+++ b/CCL/src/main/res/layout-v14/custom_notification.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CCL/src/main/res/layout/cast_activity.xml b/CCL/src/main/res/layout/cast_activity.xml
new file mode 100644
index 000000000..0769d1820
--- /dev/null
+++ b/CCL/src/main/res/layout/cast_activity.xml
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CCL/src/main/res/layout/custom_media_route_controller_controls_dialog.xml b/CCL/src/main/res/layout/custom_media_route_controller_controls_dialog.xml
new file mode 100755
index 000000000..c33b6c397
--- /dev/null
+++ b/CCL/src/main/res/layout/custom_media_route_controller_controls_dialog.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout/custom_notification.xml b/CCL/src/main/res/layout/custom_notification.xml
new file mode 100755
index 000000000..50488560d
--- /dev/null
+++ b/CCL/src/main/res/layout/custom_notification.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout/custom_tracks_dialog_layout.xml b/CCL/src/main/res/layout/custom_tracks_dialog_layout.xml
new file mode 100644
index 000000000..ccd2b6940
--- /dev/null
+++ b/CCL/src/main/res/layout/custom_tracks_dialog_layout.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout/mini_controller.xml b/CCL/src/main/res/layout/mini_controller.xml
new file mode 100644
index 000000000..65cb15971
--- /dev/null
+++ b/CCL/src/main/res/layout/mini_controller.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/layout/tracks_row_layout.xml b/CCL/src/main/res/layout/tracks_row_layout.xml
new file mode 100644
index 000000000..faba15ae7
--- /dev/null
+++ b/CCL/src/main/res/layout/tracks_row_layout.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/menu/cast_player_menu.xml b/CCL/src/main/res/menu/cast_player_menu.xml
new file mode 100644
index 000000000..6683ca733
--- /dev/null
+++ b/CCL/src/main/res/menu/cast_player_menu.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/CCL/src/main/res/values-af/strings.xml b/CCL/src/main/res/values-af/strings.xml
new file mode 100755
index 000000000..a82b22d75
--- /dev/null
+++ b/CCL/src/main/res/values-af/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast
+ Companion-biblioteek
+
+ 1.10
+
+ Speel op …
+
+ Regstreeks
+ Kanselleer
+ Aan
+ Af
+
+ Inligting nie beskikbaar nie
+
+ OK
+ Fout
+ Saai tans uit
+ na %1$s
+
+ Laai tans …
+ Geen
+ media-inligting beskikbaar nie
+
+ Probeer tans
+ vorige sessie herstel …
+
+
+ Kon nie program begin nie
+
+ Die
+ versoek om die program te begin, het verstryk!
+
+
+ Die program wat jy probeer begin, is nie op jou Chromecast-toestel beskikbaar nie
+
+
+ Kon nie die terugspeel van media begin nie
+
+ Kon
+ nie die terugspeel van media stop nie
+
+
+ Kon nie die terugspeel van media laat wag nie
+
+ \'n
+ Onbekende fout het voorgekom
+
+
+ Kon nie aan die toestel koppel nie
+
+ Kon nie die volume
+ stel nie
+
+ Daar is geen
+ verbinding met die uitsaaitoestel nie
+
+ Geen verbinding nie
+
+
+ Verbinding is verloor met die uitsaaitoestel. Program probeer tans die verbinding herstel,
+ indien moontlik. Wag \'n paar sekondes en probeer dan weer.
+
+ Kon nie die
+ handeling uitvoer nie
+
+ Kon nie met die
+ uitsaaitoestel sinkroniseer nie
+
+ Kon
+ nie media op die uitsaaitoestel laai nie
+
+ Kon nie
+ na die nuwe posisie op die uitsaaitoestel soek nie
+
+ \'n Bedienerfout
+ het by die ontvangerspeler voorgekom
+
+
+ Magtiging het uitgetel
+
+ Kon nie die
+ onderskrifstyl opdateer nie.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ar-rXB/strings.xml b/CCL/src/main/res/values-ar-rXB/strings.xml
new file mode 100755
index 000000000..d723902d3
--- /dev/null
+++ b/CCL/src/main/res/values-ar-rXB/strings.xml
@@ -0,0 +1,101 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play
+ on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting
+ to %1$s
+
+ Loading…
+
+ No media information available
+
+ Attempting
+ to recover previous session…
+
+
+ Failed to launch application
+
+
+ The request to launch the application has timed out!
+
+
+ The application you are trying to launch is not
+ available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to
+ set the volume
+
+ No
+ connection to the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application
+ is trying to re-establish the connection, if possible.
+ Please wait for a few seconds and try again.
+
+ Failed
+ to perform the action
+
+ Failed to
+ sync up with the cast device
+
+
+ Failed to load media on the cast device
+
+
+ Failed to seek to the new position on the cast
+ device
+
+ Receiver
+ player has encountered a sever error
+
+
+ Authorization timed out
+
+ Failed to
+ update the Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-bg/strings.xml b/CCL/src/main/res/values-bg/strings.xml
new file mode 100755
index 000000000..8bb9e39fb
--- /dev/null
+++ b/CCL/src/main/res/values-bg/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Игра на…
+
+ На живо
+ Отказ
+ Включено
+ Изключено
+
+ Няма налична информация
+
+ OK
+ Грешка
+ Предава се към
+ %1$s
+
+ Зарежда се…
+ Няма
+ налична информация за мултимедията
+
+ Извършва се опит
+ за възстановяване на предишна сесия...
+
+
+ Стартирането на приложението не бе успешно
+
+
+ Заявката за стартиране на приложението изтече!
+
+
+ Приложението, което се опитвате да стартирате, не е налично на устройството ви с Chromecast
+
+
+ Стартирането на възпроизвеждането на мултимедията не бе успешно
+
+
+ Спирането на възпроизвеждането на мултимедията не бе успешно
+
+
+ Поставянето на пауза на възпроизвеждането не бе успешно
+
+
+ Възникна неизвестна грешка
+
+
+ Неуспешно свързване с устройството
+
+ Задаването на
+ силата на звука не бе успешно
+
+ Няма налична
+ връзка с устройството Cast
+
+ Няма връзка
+
+
+ Връзката с устройството Cast бе изгубена. Приложението се опитва да установи повторно
+ връзка, ако е възможно. Моля, изчакайте няколко секунди и опитайте отново.
+
+ Действието не
+ бе успешно
+
+ Синхронизирането с
+ устройството Cast не бе успешно
+
+
+ Зареждането на мултимедия на устройството с Cast не бе успешно
+
+
+ Неуспешно действие на устройството Cast
+
+ Плейърът
+ приемник се натъкна на сървърна грешка
+
+
+ Упълномощаването изтече
+
+ Актуализирането на стила
+ на субтитрите не бе успешно.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ca/strings.xml b/CCL/src/main/res/values-ca/strings.xml
new file mode 100755
index 000000000..8c249da97
--- /dev/null
+++ b/CCL/src/main/res/values-ca/strings.xml
@@ -0,0 +1,99 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reprodueix
+ a…
+
+ En directe
+ Cancel·la
+ Activa
+ Desactiva
+
+ Informació no disponible
+
+ D\'acord
+ Error
+ S\'està
+ emetent a %1$s
+
+ S’està carregant…
+
+ No
+ hi ha informació disponible sobre mitjans.
+
+ S\'està provant de
+ recuperar la sessió anterior…
+
+
+ No s\'ha pogut iniciar l\'aplicació.
+
+
+ S\'ha esgotat el temps d\'espera de la sol·licitud per iniciar l\'aplicació.
+
+
+ L\'aplicació que proveu d\'iniciar no està disponible al vostre dispositiu Chromecast.
+
+ No
+ s\'ha pogut iniciar la reproducció dels mitjans.
+
+ No
+ s\'ha pogut aturar la reproducció dels mitjans.
+
+
+ No s\'ha pogut pausar la reproducció dels mitjans.
+
+ S\'ha
+ produït un problema desconegut.
+
+
+ No s\'ha pogut establir la connexió amb el dispositiu.
+
+ No s\'ha pogut
+ configurar el volum.
+
+ No hi ha cap
+ connexió disponible per al dispositiu d\'emissió.
+
+ No hi ha connexió.
+
+
+ S\'ha perdut la connexió amb el dispositiu d\'emissió. L\'aplicació està provant de
+ recuperar-la, si és possible. Espereu un moment i torneu-ho a provar.
+
+ No s\'ha pogut
+ executar l\'acció.
+
+ No s\'ha pogut fer la
+ sincronització amb el dispositiu d\'emissió.
+
+ No
+ s\'han pogut carregar els mitjans locals al dispositiu d\'emissió.
+
+ No s\'ha
+ pogut trobar una posició nova al dispositiu d\'emissió.
+
+ El reproductor
+ receptor ha detectat un error del servidor.
+
+
+ S\'ha esgotat el temps d\'espera de l\'autorització.
+
+ No s\'ha pogut
+ actualitzar l\'estil dels subtítols.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-da/strings.xml b/CCL/src/main/res/values-da/strings.xml
new file mode 100755
index 000000000..163a54262
--- /dev/null
+++ b/CCL/src/main/res/values-da/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Afspil på…
+
+ Live
+ Annuller
+ Til
+ Fra
+
+ Oplysningerne er ikke tilgængelige
+
+ OK
+ Fejl
+ Caster til
+ %1$s
+
+ Indlæser…
+ Der
+ er ingen tilgængelige medieoplysninger
+
+ Vi forsøger at
+ gendanne den forrige session…
+
+
+ Appen kunne ikke indlæses
+
+ Der
+ opstod timeout under forsøget på at indlæse appen
+
+
+ Den app, du forsøger at indlæse, er ikke tilgængelig på din Chromecast-enhed
+
+
+ Medieafspilningen kunne ikke startes
+
+
+ Medieafspilningen kunne ikke stoppes
+
+
+ Medieafspilningen kunne ikke sættes på pause
+
+ Der
+ opstod en ukendt fejl
+
+
+ Der kunne ikke oprettes forbindelse til enheden
+
+ Lydstyrken kunne
+ ikke indstilles
+
+ Der er ingen
+ forbindelse til Cast-enheden
+
+ Der er ingen forbindelse
+
+
+ Forbindelsen til Cast-enheden blev afbrudt. Appen forsøger at oprette forbindelse igen. Vent
+ et par sekunder, og prøv igen.
+
+ Handlingen
+ kunne ikke udføres
+
+ Synkronisering med
+ Cast-enheden mislykkedes
+
+
+ Medieindlæsning i Cast-enheden mislykkedes
+
+ Søgning
+ til den nye position på Cast-enheden mislykkedes
+
+
+ Modtagerafspilleren stødte på en serverfejl
+
+
+ Der opstod timeout under forsøget på godkendelse
+
+ Tekstdesignet kunne ikke
+ opdateres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-de-rAT/strings.xml b/CCL/src/main/res/values-de-rAT/strings.xml
new file mode 100755
index 000000000..1edb50f59
--- /dev/null
+++ b/CCL/src/main/res/values-de-rAT/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Wiedergeben
+ auf...
+
+ Live
+ Abbrechen
+ An
+ Aus
+
+ Informationen nicht verfügbar
+
+ OK
+ Fehler
+ Übertragung an
+ %1$s
+
+ Wird geladen...
+
+ Keine Medieninformationen verfügbar
+
+ Es wird versucht,
+ die vorherige Sitzung wiederherzustellen...
+
+
+ Die App konnte nicht gestartet werden.
+
+ Bei
+ der Anfrage zum Starten der App ist eine Zeitüberschreitung aufgetreten.
+
+
+ Die App, die Sie starten möchten, ist auf Ihrem Chromecast-Gerät nicht verfügbar.
+
+
+ Die Medienwiedergabe konnte nicht gestartet werden.
+
+ Die
+ Medienwiedergabe konnte nicht angehalten werden.
+
+
+ Die Medienwiedergabe konnte nicht pausiert werden.
+
+ Ein
+ unbekannter Fehler ist aufgetreten.
+
+
+ Es konnte keine Verbindung zum Gerät hergestellt werden.
+
+ Die Lautstärke
+ konnte nicht eingestellt werden.
+
+ Es besteht keine
+ Verbindung zum Übertragungsgerät.
+
+ Keine Verbindung
+
+
+ Die Verbindung zum Übertragungsgerät wurde getrennt. Die App möchte die Verbindung nach
+ Möglichkeit wiederherstellen. Warten Sie einige Sekunden und versuchen Sie es dann erneut.
+
+ Die Aktion
+ konnte nicht ausgeführt werden.
+
+ Die Synchronisation mit
+ dem Übertragungsgerät konnte nicht durchgeführt werden.
+
+ Die
+ Medien konnten nicht auf dem Übertragungsgerät geladen werden.
+
+ Die neue
+ Position konnte auf dem Übertragungsgerät nicht festgelegt werden.
+
+ Beim
+ Empfänger-Player ist ein Serverfehler aufgetreten.
+
+
+ Zeitüberschreitung bei der Autorisierung
+
+ Der Untertitelstil konnte
+ nicht aktualisiert werden.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-de-rCH/strings.xml b/CCL/src/main/res/values-de-rCH/strings.xml
new file mode 100755
index 000000000..1edb50f59
--- /dev/null
+++ b/CCL/src/main/res/values-de-rCH/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Wiedergeben
+ auf...
+
+ Live
+ Abbrechen
+ An
+ Aus
+
+ Informationen nicht verfügbar
+
+ OK
+ Fehler
+ Übertragung an
+ %1$s
+
+ Wird geladen...
+
+ Keine Medieninformationen verfügbar
+
+ Es wird versucht,
+ die vorherige Sitzung wiederherzustellen...
+
+
+ Die App konnte nicht gestartet werden.
+
+ Bei
+ der Anfrage zum Starten der App ist eine Zeitüberschreitung aufgetreten.
+
+
+ Die App, die Sie starten möchten, ist auf Ihrem Chromecast-Gerät nicht verfügbar.
+
+
+ Die Medienwiedergabe konnte nicht gestartet werden.
+
+ Die
+ Medienwiedergabe konnte nicht angehalten werden.
+
+
+ Die Medienwiedergabe konnte nicht pausiert werden.
+
+ Ein
+ unbekannter Fehler ist aufgetreten.
+
+
+ Es konnte keine Verbindung zum Gerät hergestellt werden.
+
+ Die Lautstärke
+ konnte nicht eingestellt werden.
+
+ Es besteht keine
+ Verbindung zum Übertragungsgerät.
+
+ Keine Verbindung
+
+
+ Die Verbindung zum Übertragungsgerät wurde getrennt. Die App möchte die Verbindung nach
+ Möglichkeit wiederherstellen. Warten Sie einige Sekunden und versuchen Sie es dann erneut.
+
+ Die Aktion
+ konnte nicht ausgeführt werden.
+
+ Die Synchronisation mit
+ dem Übertragungsgerät konnte nicht durchgeführt werden.
+
+ Die
+ Medien konnten nicht auf dem Übertragungsgerät geladen werden.
+
+ Die neue
+ Position konnte auf dem Übertragungsgerät nicht festgelegt werden.
+
+ Beim
+ Empfänger-Player ist ein Serverfehler aufgetreten.
+
+
+ Zeitüberschreitung bei der Autorisierung
+
+ Der Untertitelstil konnte
+ nicht aktualisiert werden.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-de/strings.xml b/CCL/src/main/res/values-de/strings.xml
new file mode 100755
index 000000000..1edb50f59
--- /dev/null
+++ b/CCL/src/main/res/values-de/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Wiedergeben
+ auf...
+
+ Live
+ Abbrechen
+ An
+ Aus
+
+ Informationen nicht verfügbar
+
+ OK
+ Fehler
+ Übertragung an
+ %1$s
+
+ Wird geladen...
+
+ Keine Medieninformationen verfügbar
+
+ Es wird versucht,
+ die vorherige Sitzung wiederherzustellen...
+
+
+ Die App konnte nicht gestartet werden.
+
+ Bei
+ der Anfrage zum Starten der App ist eine Zeitüberschreitung aufgetreten.
+
+
+ Die App, die Sie starten möchten, ist auf Ihrem Chromecast-Gerät nicht verfügbar.
+
+
+ Die Medienwiedergabe konnte nicht gestartet werden.
+
+ Die
+ Medienwiedergabe konnte nicht angehalten werden.
+
+
+ Die Medienwiedergabe konnte nicht pausiert werden.
+
+ Ein
+ unbekannter Fehler ist aufgetreten.
+
+
+ Es konnte keine Verbindung zum Gerät hergestellt werden.
+
+ Die Lautstärke
+ konnte nicht eingestellt werden.
+
+ Es besteht keine
+ Verbindung zum Übertragungsgerät.
+
+ Keine Verbindung
+
+
+ Die Verbindung zum Übertragungsgerät wurde getrennt. Die App möchte die Verbindung nach
+ Möglichkeit wiederherstellen. Warten Sie einige Sekunden und versuchen Sie es dann erneut.
+
+ Die Aktion
+ konnte nicht ausgeführt werden.
+
+ Die Synchronisation mit
+ dem Übertragungsgerät konnte nicht durchgeführt werden.
+
+ Die
+ Medien konnten nicht auf dem Übertragungsgerät geladen werden.
+
+ Die neue
+ Position konnte auf dem Übertragungsgerät nicht festgelegt werden.
+
+ Beim
+ Empfänger-Player ist ein Serverfehler aufgetreten.
+
+
+ Zeitüberschreitung bei der Autorisierung
+
+ Der Untertitelstil konnte
+ nicht aktualisiert werden.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rGB/strings.xml b/CCL/src/main/res/values-en-rGB/strings.xml
new file mode 100755
index 000000000..560890ecc
--- /dev/null
+++ b/CCL/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting to
+ %1$s
+
+ Loading…
+ No
+ media information available
+
+ Attempting to
+ recover previous session…
+
+
+ Failed to launch application
+
+ The
+ request to launch the application has timed out!
+
+
+ The application you are trying to launch is not available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to set the
+ volume
+
+ No connection to
+ the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application is trying to re-establish the
+ connection, if possible. Please wait for a few seconds and try again.
+
+ Failed to
+ perform the action
+
+ Failed to sync up with
+ the cast device
+
+
+ Failed to load media on the cast device
+
+ Failed
+ to seek to the new position on the cast device
+
+ Receiver player
+ has encountered a sever error
+
+
+ Authorisation timed out
+
+ Failed to update the
+ Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rIE/strings.xml b/CCL/src/main/res/values-en-rIE/strings.xml
new file mode 100755
index 000000000..560890ecc
--- /dev/null
+++ b/CCL/src/main/res/values-en-rIE/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting to
+ %1$s
+
+ Loading…
+ No
+ media information available
+
+ Attempting to
+ recover previous session…
+
+
+ Failed to launch application
+
+ The
+ request to launch the application has timed out!
+
+
+ The application you are trying to launch is not available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to set the
+ volume
+
+ No connection to
+ the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application is trying to re-establish the
+ connection, if possible. Please wait for a few seconds and try again.
+
+ Failed to
+ perform the action
+
+ Failed to sync up with
+ the cast device
+
+
+ Failed to load media on the cast device
+
+ Failed
+ to seek to the new position on the cast device
+
+ Receiver player
+ has encountered a sever error
+
+
+ Authorisation timed out
+
+ Failed to update the
+ Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rIN/strings.xml b/CCL/src/main/res/values-en-rIN/strings.xml
new file mode 100755
index 000000000..560890ecc
--- /dev/null
+++ b/CCL/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting to
+ %1$s
+
+ Loading…
+ No
+ media information available
+
+ Attempting to
+ recover previous session…
+
+
+ Failed to launch application
+
+ The
+ request to launch the application has timed out!
+
+
+ The application you are trying to launch is not available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to set the
+ volume
+
+ No connection to
+ the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application is trying to re-establish the
+ connection, if possible. Please wait for a few seconds and try again.
+
+ Failed to
+ perform the action
+
+ Failed to sync up with
+ the cast device
+
+
+ Failed to load media on the cast device
+
+ Failed
+ to seek to the new position on the cast device
+
+ Receiver player
+ has encountered a sever error
+
+
+ Authorisation timed out
+
+ Failed to update the
+ Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rSG/strings.xml b/CCL/src/main/res/values-en-rSG/strings.xml
new file mode 100755
index 000000000..560890ecc
--- /dev/null
+++ b/CCL/src/main/res/values-en-rSG/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting to
+ %1$s
+
+ Loading…
+ No
+ media information available
+
+ Attempting to
+ recover previous session…
+
+
+ Failed to launch application
+
+ The
+ request to launch the application has timed out!
+
+
+ The application you are trying to launch is not available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to set the
+ volume
+
+ No connection to
+ the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application is trying to re-establish the
+ connection, if possible. Please wait for a few seconds and try again.
+
+ Failed to
+ perform the action
+
+ Failed to sync up with
+ the cast device
+
+
+ Failed to load media on the cast device
+
+ Failed
+ to seek to the new position on the cast device
+
+ Receiver player
+ has encountered a sever error
+
+
+ Authorisation timed out
+
+ Failed to update the
+ Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rXA/strings.xml b/CCL/src/main/res/values-en-rXA/strings.xml
new file mode 100755
index 000000000..1ab92f926
--- /dev/null
+++ b/CCL/src/main/res/values-en-rXA/strings.xml
@@ -0,0 +1,103 @@
+
+ [Çåšţ Çömþåñîöñ Ļîбŕåŕý
+ one two three]
+
+ [1.10 one]
+
+ [Þļåý öñ…
+ one]
+
+ [Ļîvé one]
+ [Çåñçéļ one]
+ [Öñ one]
+ [Öƒƒ one]
+
+ [Îñƒöŕmåţîöñ Ñöţ Åvåîļåбļé one two three]
+
+ [ÖĶ one]
+ [Éŕŕöŕ one]
+ [Çåšţîñĝ ţö
+ ᐅ%1$sᐊ one two three]
+
+ [Ļöåðîñĝ… one]
+ [Ñö
+ méðîå îñƒöŕmåţîöñ åvåîļåбļé one two three four five six seven]
+
+ [Åţţémþţîñĝ ţö
+ ŕéçövéŕ þŕévîöûš šéššîöñ… one two three four five six seven eight]
+
+
+ [Fåîļéð ţö ļåûñçĥ åþþļîçåţîöñ one two three four five six]
+
+ [Ţĥé
+ ŕéqûéšţ ţö ļåûñçĥ ţĥé åþþļîçåţîöñ ĥåš ţîméð öûţ¡ one two three four five six seven eight
+ nine ten eleven]
+
+
+ [Ţĥé åþþļîçåţîöñ ýöû åŕé ţŕýîñĝ ţö ļåûñçĥ îš ñöţ åvåîļåбļé öñ ýöûŕ Çĥŕöméçåšţ ðévîçé one two
+ three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen]
+
+
+ [Fåîļéð ţö šţåŕţ ţĥé þļåýбåçķ öƒ méðîå one two three four five six seven eight]
+
+
+ [Fåîļéð ţö šţöþ ţĥé þļåýбåçķ öƒ méðîå one two three four five six seven eight]
+
+
+ [Fåîļéð ţö þåûšé ţĥé þļåýбåçķ öƒ méðîå one two three four five six seven eight]
+
+ [Åñ
+ ûñķñöŵñ éŕŕöŕ ŵåš éñçöûñţéŕéð one two three four five six seven]
+
+
+ [Çöûļð ñöţ çöññéçţ ţö ţĥé ðévîçé one two three four five six seven]
+
+ [Fåîļéð ţö šéţ ţĥé
+ vöļûmé one two three four five]
+
+ [Ñö çöññéçţîöñ ţö
+ ţĥé çåšţ ðévîçé îš þŕéšéñţ one two three four five six seven eight nine]
+
+ [Ñö çöññéçţîöñ one two]
+
+
+ [Çöññéçţîöñ ţö ţĥé çåšţ ðévîçé ĥåš бééñ ļöšţ. Åþþļîçåţîöñ îš ţŕýîñĝ ţö ŕé-éšţåбļîšĥ ţĥé
+ çöññéçţîöñ, îƒ þöššîбļé. Þļéåšé ŵåîţ ƒöŕ å ƒéŵ šéçöñðš åñð ţŕý åĝåîñ. one two three four
+ five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen
+ eighteen nineteen twenty twentyone twentytwo twentythree]
+
+ [Fåîļéð ţö
+ þéŕƒöŕm ţĥé åçţîöñ one two three four five six]
+
+ [Fåîļéð ţö šýñç ûþ ŵîţĥ
+ ţĥé çåšţ ðévîçé one two three four five six seven eight]
+
+
+ [Fåîļéð ţö ļöåð méðîå öñ ţĥé çåšţ ðévîçé one two three four five six seven eight]
+
+ [Fåîļéð
+ ţö šééķ ţö ţĥé ñéŵ þöšîţîöñ öñ ţĥé çåšţ ðévîçé one two three four five six seven eight nine
+ ten eleven]
+
+ [Ŕéçéîvéŕ þļåýéŕ
+ ĥåš éñçöûñţéŕéð å šévéŕ éŕŕöŕ one two three four five six seven eight nine]
+
+
+ [Åûţĥöŕîžåţîöñ ţîméð öûţ one two three]
+
+ [Fåîļéð ţö ûþðåţé ţĥé
+ Çåþţîöñš šţýļé. one two three four five six seven eight]
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rXC/strings.xml b/CCL/src/main/res/values-en-rXC/strings.xml
new file mode 100755
index 000000000..ef2445c15
--- /dev/null
+++ b/CCL/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,139 @@
+
+
+ Cast
+ Companion Library
+
+
+ 1.10
+
+
+ Play
+ on…
+
+
+ Live
+
+
+ Cancel
+
+
+ On
+
+
+ Off
+
+
+ Information
+ Not Available
+
+
+ OK
+
+
+ Error
+
+
+ Casting
+ to %1$s
+
+
+ Loading…
+
+
+ No
+ media information available
+
+
+ Attempting
+ to recover previous session…
+
+
+ Failed
+ to launch application
+
+
+ The
+ request to launch the application has timed out!
+
+
+ The
+ application you are trying to launch is not available on your Chromecast device
+
+
+ Failed
+ to start the playback of media
+
+
+ Failed
+ to stop the playback of media
+
+
+ Failed
+ to pause the playback of media
+
+
+ An
+ unknown error was encountered
+
+
+ Could
+ not connect to the device
+
+
+ Failed
+ to set the volume
+
+
+ No
+ connection to the cast device is present
+
+
+ No
+ connection
+
+
+ Connection
+ to the cast device has been lost. Application is trying to re-establish the connection, if
+ possible. Please wait for a few seconds and try again.
+
+
+ Failed
+ to perform the action
+
+
+ Failed
+ to sync up with the cast device
+
+
+ Failed
+ to load media on the cast device
+
+
+ Failed
+ to seek to the new position on the cast device
+
+
+ Receiver
+ player has encountered a sever error
+
+
+ Authorization
+ timed out
+
+
+ Failed
+ to update the Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-en-rZA/strings.xml b/CCL/src/main/res/values-en-rZA/strings.xml
new file mode 100755
index 000000000..560890ecc
--- /dev/null
+++ b/CCL/src/main/res/values-en-rZA/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Play on…
+
+ Live
+ Cancel
+ On
+ Off
+
+ Information Not Available
+
+ OK
+ Error
+ Casting to
+ %1$s
+
+ Loading…
+ No
+ media information available
+
+ Attempting to
+ recover previous session…
+
+
+ Failed to launch application
+
+ The
+ request to launch the application has timed out!
+
+
+ The application you are trying to launch is not available on your Chromecast device
+
+
+ Failed to start the playback of media
+
+
+ Failed to stop the playback of media
+
+
+ Failed to pause the playback of media
+
+ An
+ unknown error was encountered
+
+
+ Could not connect to the device
+
+ Failed to set the
+ volume
+
+ No connection to
+ the cast device is present
+
+ No connection
+
+
+ Connection to the cast device has been lost. Application is trying to re-establish the
+ connection, if possible. Please wait for a few seconds and try again.
+
+ Failed to
+ perform the action
+
+ Failed to sync up with
+ the cast device
+
+
+ Failed to load media on the cast device
+
+ Failed
+ to seek to the new position on the cast device
+
+ Receiver player
+ has encountered a sever error
+
+
+ Authorisation timed out
+
+ Failed to update the
+ Captions style.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rAR/strings.xml b/CCL/src/main/res/values-es-rAR/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rAR/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rBO/strings.xml b/CCL/src/main/res/values-es-rBO/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rBO/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rCL/strings.xml b/CCL/src/main/res/values-es-rCL/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rCL/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rCO/strings.xml b/CCL/src/main/res/values-es-rCO/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rCO/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rCR/strings.xml b/CCL/src/main/res/values-es-rCR/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rCR/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rDO/strings.xml b/CCL/src/main/res/values-es-rDO/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rDO/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rEC/strings.xml b/CCL/src/main/res/values-es-rEC/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rEC/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rES/strings.xml b/CCL/src/main/res/values-es-rES/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rGT/strings.xml b/CCL/src/main/res/values-es-rGT/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rGT/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rHN/strings.xml b/CCL/src/main/res/values-es-rHN/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rHN/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rMX/strings.xml b/CCL/src/main/res/values-es-rMX/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rNI/strings.xml b/CCL/src/main/res/values-es-rNI/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rNI/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rPA/strings.xml b/CCL/src/main/res/values-es-rPA/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rPA/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rPE/strings.xml b/CCL/src/main/res/values-es-rPE/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rPE/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rPR/strings.xml b/CCL/src/main/res/values-es-rPR/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rPR/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rPY/strings.xml b/CCL/src/main/res/values-es-rPY/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rPY/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rSV/strings.xml b/CCL/src/main/res/values-es-rSV/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rSV/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rUS/strings.xml b/CCL/src/main/res/values-es-rUS/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rUY/strings.xml b/CCL/src/main/res/values-es-rUY/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rUY/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es-rVE/strings.xml b/CCL/src/main/res/values-es-rVE/strings.xml
new file mode 100755
index 000000000..69b9b2302
--- /dev/null
+++ b/CCL/src/main/res/values-es-rVE/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproducir
+ en…
+
+ En vivo
+ Cancelar
+ Activado
+ Desactivado
+ La
+ información no está disponible.
+
+ Aceptar
+ Error
+ Transmitir a
+ %1$s
+
+ Cargando…
+ La
+ información de medios no está disponible.
+
+ Intentando
+ recuperar la sesión anterior...
+
+
+ Error al iniciar la aplicación
+
+ El
+ tiempo para solicitar el inicio de la aplicación se agotó.
+
+
+ La aplicación que intenta iniciar no está disponible en su dispositivo Chromecast.
+
+
+ Error al iniciar la reproducción de medios
+
+
+ Error al finalizar la reproducción de medios
+
+
+ Error al pausar la reproducción de medios
+
+ Se
+ detectó un error desconocido.
+
+
+ No se pudo establecer la conexión con el dispositivo.
+
+ Error al definir
+ el volumen
+
+ No hay conexión
+ con el dispositivo de transmisión.
+
+ Sin conexión
+
+
+ Se perdió la conexión con el dispositivo de transmisión. La aplicación está intentando
+ restablecer la conexión. Espere unos segundos e inténtelo nuevamente.
+
+ Error al
+ realizar la acción
+
+ Error al sincronizar
+ con el dispositivo de transmisión
+
+
+ Error al cargar los medios en el dispositivo de transmisión
+
+ Error al
+ buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ que recibe la transmisión detectó un error del servidor.
+
+ El
+ tiempo para la autorización se agotó.
+
+ Error al actualizar el
+ estilo de los subtítulos
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-es/strings.xml b/CCL/src/main/res/values-es/strings.xml
new file mode 100755
index 000000000..6ac42c1f1
--- /dev/null
+++ b/CCL/src/main/res/values-es/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Jugar en…
+
+ En directo
+ Cancelar
+ Activar
+ Desactivar
+ La
+ información no está disponible
+
+ Aceptar
+ Error
+ Transmitiendo
+ a %1$s
+
+ Cargando…
+ No
+ hay información de medios disponible
+
+ Intentando
+ recuperar la sesión anterior…
+
+
+ No se ha podido ejecutar la aplicación
+
+ Se
+ ha agotado el tiempo de espera para solicitar la ejecución de la aplicación
+
+
+ La aplicación que estás intentando ejecutar no está disponible en tu dispositivo Chromecast
+
+ No
+ se ha podido iniciar la reproducción de los medios
+
+ No
+ se ha podido detener la reproducción de los medios
+
+
+ No se ha podido pausar la reproducción de los medios
+
+ Se ha
+ detectado un error desconocido
+
+
+ No se ha podido conectar con el dispositivo.
+
+ No se ha podido
+ establecer el volumen
+
+ No hay ninguna
+ conexión al dispositivo de transmisión
+
+ Sin conexión
+
+
+ Se ha perdido la conexión al dispositivo de transmisión. La aplicación está intentando
+ volver a establecer la conexión, si es posible. Espera unos segundos e inténtalo de nuevo.
+
+ No se ha
+ podido llevar a cabo la acción
+
+ No se ha podido
+ sincronizar con el dispositivo de transmisión
+
+ No
+ se han podido cargar los medios en el dispositivo de transmisión
+
+ No se ha
+ podido buscar la nueva posición en el dispositivo de transmisión
+
+ El reproductor
+ receptor ha detectado un error del servidor
+
+ Se
+ ha agotado el tiempo de espera de autorización
+
+ No se ha podido
+ actualizar el estilo de los subtítulos.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-et/strings.xml b/CCL/src/main/res/values-et/strings.xml
new file mode 100755
index 000000000..2ad53c12d
--- /dev/null
+++ b/CCL/src/main/res/values-et/strings.xml
@@ -0,0 +1,98 @@
+
+ Casti lisaseadmete
+ kogu
+
+ 1.10
+
+ Esita seadmes
+ …
+
+ Aktiivne
+ Tühista
+ Sees
+ Väljas
+
+ Teave pole saadaval
+
+ OK
+ Viga
+ Ülekandmine
+ seadmesse %1$s
+
+ Laadimine …
+
+ Meediateave pole saadaval
+
+ Proovitakse
+ taastada eelmist seanssi …
+
+
+ Rakenduse käivitamine ebaõnnestus
+
+
+ Rakenduse käivitamise taotlus on aegunud.
+
+
+ Rakendus, mida püüate käivitada, pole Chromecasti seadmes saadaval
+
+
+ Meedia taasesituse alustamine ebaõnnestus
+
+
+ Meedia taasesituse peatamine ebaõnnestus
+
+
+ Meedia taasesituse peatamine ebaõnnestus
+
+ Ilmnes
+ tundmatu viga
+
+
+ Seadmega ei õnnestunud ühendust luua
+
+ Helitugevuse
+ määramine ebaõnnestus
+
+ Ühendus
+ ülekandeseadmega puudub
+
+ Ühendus puudub
+
+
+ Ühendus ülekandeseadmega on katkenud. Rakendus püüab võimaluse korral ühendust uuesti luua.
+ Oodake mõni sekund ja proovige uuesti.
+
+ Toimingu
+ tegemine ebaõnnestus
+
+ Ülekandeseadmega
+ sünkroonimine ebaõnnestus
+
+
+ Meediat ei õnnestunud ülekandeseadmesse laadida
+
+
+ Ülekandeseadme uue positsiooni tuvastamine ebaõnnestus
+
+ Vastuvõtvas
+ pleieris ilmnes serveri viga
+
+
+ Autoriseerimine on aegunud
+
+ Tiitrite stiili
+ värskendamine ebaõnnestus.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-fi/strings.xml b/CCL/src/main/res/values-fi/strings.xml
new file mode 100755
index 000000000..15b5b0419
--- /dev/null
+++ b/CCL/src/main/res/values-fi/strings.xml
@@ -0,0 +1,96 @@
+
+ Suoratoistokirjasto
+
+ 1.10
+
+ Toista...
+
+ Suora
+ Peruuta
+ Päällä
+ Ei päällä
+
+ Tiedot eivät ole saatavana
+
+ OK
+ Virhe
+
+ Suoratoistetaan laitteeseen %1$s
+
+ Ladataan…
+
+ Mediatiedot eivät ole käytettävissä
+
+ Edellistä istuntoa
+ yritetään palauttaa...
+
+
+ Sovelluksen käynnistäminen epäonnistui
+
+
+ Sovelluksen käynnistyspyyntö aikakatkaistiin!
+
+
+ Sovellus, jota yrität käynnistää, ei ole käytettävissä Chromecast-laitteessasi
+
+
+ Median toiston aloitus epäonnistui
+
+
+ Median toiston pysäytys epäonnistui
+
+
+ Median toiston keskeytys epäonnistui
+
+
+ Tuntematon virhe ilmeni
+
+
+ Laitteeseen ei saatu yhteyttä
+
+ Äänenvoimakkuuden
+ asettaminen epäonnistui
+
+
+ Suoratoistolaitteeseen ei ole yhteyttä
+
+ Ei yhteyttä
+
+
+ Yhteys suoratoistolaitteeseen menetettiin. Sovellus yrittää muodostaa yhteyden uudelleen.
+ Odota muutama sekunti ja yritä uudelleen.
+
+ Toiminto
+ epäonnistui
+
+ Synkronointi
+ suoratoistolaitteen kanssa epäonnistui
+
+
+ Median lataaminen suoratoistolaitteeseen epäonnistui
+
+ Uuden
+ paikan hakeminen suoratoistolaitteessa epäonnistui
+
+ Vakava virhe
+ vastaanottimen soittimessa
+
+
+ Valtuutus aikakatkaistiin
+
+ Tekstityksen tyylin
+ päivittäminen epäonnistui.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-fil/strings.xml b/CCL/src/main/res/values-fil/strings.xml
new file mode 100755
index 000000000..440f7048a
--- /dev/null
+++ b/CCL/src/main/res/values-fil/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ I-play sa
+ ...
+
+ Live
+ Kanselahin
+ I-on
+ I-off
+
+ Hindi Available ang Impormasyon
+
+ OK
+ Error
+ Nagka-cast sa
+ %1$s
+
+ Naglo-load…
+
+ Walang available na impormasyon ng media
+
+ Sinusubukang
+ i-recover ang nakaraang session...
+
+
+ Hindi nailunsad ang application
+
+
+ Nag-timeout ang kahilingan upang ilunsad ang application!
+
+
+ Hindi available sa iyong Chromecast device ang application na sinusubukan mong ilunsad
+
+
+ Hindi nasimulan ang pag-playback ng media
+
+
+ Hindi nahinto ang pag-playback ng media
+
+
+ Hindi na-pause ang pag-playback ng media
+
+
+ Nagkaroon ng hindi kilalang error
+
+
+ Hindi makakonekta sa device
+
+ Hindi naitakda ang
+ volume
+
+ Walang nakitang
+ koneksyon sa cast device
+
+ Walang koneksyon
+
+
+ Nawala ang koneksyon sa cast device. Sinusubukan ng application na makagawa muli ng
+ koneksyon, kung maaari. Mangyaring maghintay ng ilang segundo at subukang muli.
+
+ Hindi nagawa
+ ang pagkilos
+
+ Hindi nakapag-sync sa
+ cast device
+
+
+ Hindi na-load ang media sa cast device
+
+ Hindi
+ nahanap ang bagong posisyon sa cast device
+
+ Nagkaroon ng
+ error sa server ang receiver player
+
+
+ Nag-time out ang pagpapahintulot
+
+ Hindi na-update ang
+ estilo ng Mga Caption.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-fr-rCA/strings.xml b/CCL/src/main/res/values-fr-rCA/strings.xml
new file mode 100755
index 000000000..d50003e24
--- /dev/null
+++ b/CCL/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,99 @@
+
+ Bibliothèque de
+ compagnon Cast
+
+ 1.10
+
+ Visionner
+ sur…
+
+ En temps réel
+ Annuler
+ Activée
+ Désactivée
+
+ Information non disponible
+
+ OK
+ Erreur
+ Diffusion sur
+ %1$s en cours
+
+ Chargement en cours…
+
+
+ Aucune information sur le média disponible
+
+ Tentative de
+ récupération de la session précédente…
+
+
+ Échec de l\'ouverture de l\'application
+
+ La
+ demande d\'ouverture de l\'application a expiré!
+
+
+ L\'application que vous tentez d\'ouvrir n\'est pas disponible sur votre appareil Chromecast
+
+
+ Échec du lancement de la lecture du média
+
+
+ Échec de l\'arrêt de la lecture du média
+
+
+ Échec de la mise sur pause de la lecture du média
+
+ Une
+ erreur inconnue est survenue
+
+
+ Impossible de se connecter à l\'appareil
+
+ Échec de réglage
+ du volume
+
+ Aucune connexion à
+ l\'appareil Cast
+
+ Aucune connexion
+
+
+ Perte de la connexion à l\'appareil Cast. L\'application tente de rétablir la connexion si
+ cela est possible. Veuillez attendre quelques secondes, puis réessayer.
+
+ Impossible
+ d\'effectuer cette action
+
+ Échec de la
+ synchronisation avec l\'appareil Cast
+
+
+ Échec du chargement de contenu sur l\'appareil Cast
+
+ Échec de
+ recherche d\'une nouvelle position dans l\'appareil Cast
+
+ Le
+ lecteur-récepteur a rencontré une erreur grave
+
+
+ L\'autorisation est échue
+
+ Échec de la mise à jour
+ du style des sous-titres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-fr-rCH/strings.xml b/CCL/src/main/res/values-fr-rCH/strings.xml
new file mode 100755
index 000000000..92d8666cc
--- /dev/null
+++ b/CCL/src/main/res/values-fr-rCH/strings.xml
@@ -0,0 +1,99 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Lire sur…
+
+ Disponible en ligne
+ Annuler
+ Activer
+ Désactiver
+
+ Informations non disponibles.
+
+ OK
+ Erreur
+ Cast vers %1$s
+ en cours...
+
+ Chargement en cours…
+
+
+ Aucune information sur le média disponible.
+
+ Tentative de
+ récupération de la session précédente en cours…
+
+
+ Impossible de lancer l\'application.
+
+ La
+ demande de lancement de l\'application a expiré.
+
+
+ L\'application que vous essayez de lancer n\'est pas disponible sur votre appareil
+ Chromecast.
+
+
+ Impossible de lancer la lecture du média.
+
+
+ Impossible d\'arrêter la lecture du média.
+
+
+ Impossible de mettre sur pause la lecture du média.
+
+ Une
+ erreur inconnue a été détectée.
+
+
+ Connexion impossible à l\'appareil.
+
+ Impossible de
+ régler le volume.
+
+ Aucune connexion à
+ l\'appareil Cast n\'est disponible.
+
+ Aucune connexion.
+
+
+ La connexion à l\'appareil Cast a été perdue. L\'application essaye à nouveau d\'établir une
+ connexion, si possible. Veuillez patienter quelques secondes et réessayer.
+
+ Impossible
+ d\'effectuer l\'action.
+
+ Impossible d\'effectuer
+ la synchronisation avec l\'appareil Cast.
+
+
+ Impossible de charger le média sur l\'appareil Cast.
+
+
+ Impossible de rechercher la nouvelle position sur l\'appareil Cast.
+
+ Le lecteur de
+ l\'appareil de réception a rencontré une erreur de serveur.
+
+
+ Expiration de l\'autorisation
+
+ Impossible de mettre à
+ jour le style des sous-titres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-fr/strings.xml b/CCL/src/main/res/values-fr/strings.xml
new file mode 100755
index 000000000..92d8666cc
--- /dev/null
+++ b/CCL/src/main/res/values-fr/strings.xml
@@ -0,0 +1,99 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Lire sur…
+
+ Disponible en ligne
+ Annuler
+ Activer
+ Désactiver
+
+ Informations non disponibles.
+
+ OK
+ Erreur
+ Cast vers %1$s
+ en cours...
+
+ Chargement en cours…
+
+
+ Aucune information sur le média disponible.
+
+ Tentative de
+ récupération de la session précédente en cours…
+
+
+ Impossible de lancer l\'application.
+
+ La
+ demande de lancement de l\'application a expiré.
+
+
+ L\'application que vous essayez de lancer n\'est pas disponible sur votre appareil
+ Chromecast.
+
+
+ Impossible de lancer la lecture du média.
+
+
+ Impossible d\'arrêter la lecture du média.
+
+
+ Impossible de mettre sur pause la lecture du média.
+
+ Une
+ erreur inconnue a été détectée.
+
+
+ Connexion impossible à l\'appareil.
+
+ Impossible de
+ régler le volume.
+
+ Aucune connexion à
+ l\'appareil Cast n\'est disponible.
+
+ Aucune connexion.
+
+
+ La connexion à l\'appareil Cast a été perdue. L\'application essaye à nouveau d\'établir une
+ connexion, si possible. Veuillez patienter quelques secondes et réessayer.
+
+ Impossible
+ d\'effectuer l\'action.
+
+ Impossible d\'effectuer
+ la synchronisation avec l\'appareil Cast.
+
+
+ Impossible de charger le média sur l\'appareil Cast.
+
+
+ Impossible de rechercher la nouvelle position sur l\'appareil Cast.
+
+ Le lecteur de
+ l\'appareil de réception a rencontré une erreur de serveur.
+
+
+ Expiration de l\'autorisation
+
+ Impossible de mettre à
+ jour le style des sous-titres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-gsw/strings.xml b/CCL/src/main/res/values-gsw/strings.xml
new file mode 100755
index 000000000..1edb50f59
--- /dev/null
+++ b/CCL/src/main/res/values-gsw/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Wiedergeben
+ auf...
+
+ Live
+ Abbrechen
+ An
+ Aus
+
+ Informationen nicht verfügbar
+
+ OK
+ Fehler
+ Übertragung an
+ %1$s
+
+ Wird geladen...
+
+ Keine Medieninformationen verfügbar
+
+ Es wird versucht,
+ die vorherige Sitzung wiederherzustellen...
+
+
+ Die App konnte nicht gestartet werden.
+
+ Bei
+ der Anfrage zum Starten der App ist eine Zeitüberschreitung aufgetreten.
+
+
+ Die App, die Sie starten möchten, ist auf Ihrem Chromecast-Gerät nicht verfügbar.
+
+
+ Die Medienwiedergabe konnte nicht gestartet werden.
+
+ Die
+ Medienwiedergabe konnte nicht angehalten werden.
+
+
+ Die Medienwiedergabe konnte nicht pausiert werden.
+
+ Ein
+ unbekannter Fehler ist aufgetreten.
+
+
+ Es konnte keine Verbindung zum Gerät hergestellt werden.
+
+ Die Lautstärke
+ konnte nicht eingestellt werden.
+
+ Es besteht keine
+ Verbindung zum Übertragungsgerät.
+
+ Keine Verbindung
+
+
+ Die Verbindung zum Übertragungsgerät wurde getrennt. Die App möchte die Verbindung nach
+ Möglichkeit wiederherstellen. Warten Sie einige Sekunden und versuchen Sie es dann erneut.
+
+ Die Aktion
+ konnte nicht ausgeführt werden.
+
+ Die Synchronisation mit
+ dem Übertragungsgerät konnte nicht durchgeführt werden.
+
+ Die
+ Medien konnten nicht auf dem Übertragungsgerät geladen werden.
+
+ Die neue
+ Position konnte auf dem Übertragungsgerät nicht festgelegt werden.
+
+ Beim
+ Empfänger-Player ist ein Serverfehler aufgetreten.
+
+
+ Zeitüberschreitung bei der Autorisierung
+
+ Der Untertitelstil konnte
+ nicht aktualisiert werden.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-hr/strings.xml b/CCL/src/main/res/values-hr/strings.xml
new file mode 100755
index 000000000..a8c6d518a
--- /dev/null
+++ b/CCL/src/main/res/values-hr/strings.xml
@@ -0,0 +1,97 @@
+
+ Popratna biblioteka za
+ emitiranje
+
+ 1.10
+
+ Pokreni na…
+
+ Uživo
+ Odustani
+ Uključeno
+ Isključeno
+
+ Podaci nisu dostupni
+
+ U redu
+ Pogreška
+ Emitiranje na
+ uređaju %1$s
+
+ Učitavanje…
+ Nema
+ dostupnih podataka o medijskom sadržaju
+
+ Pokušaj vraćanja
+ prethodne sesije...
+
+
+ Pokretanje aplikacije nije uspjelo
+
+
+ Zahtjev za pokretanje aplikacije istekao je.
+
+
+ Aplikacija koju pokušavate pokrenuti nije dostupna na vašem Chromecast uređaju
+
+
+ Pokretanje reprodukcije medijskog sadržaja nije uspjelo
+
+
+ Zaustavljanje reprodukcije medijskog sadržaja nije uspjelo
+
+
+ Pauziranje reprodukcije medijskog sadržaja nije uspjelo
+
+ Došlo
+ je do nepoznate pogreške
+
+
+ Povezivanje s uređajem nije uspjelo
+
+ Postavljanje
+ glasnoće nije uspjelo
+
+ Ne postoji veza s
+ uređajem za emitiranje
+
+ Niste povezani
+
+
+ Izgubljena je veza s uređajem za emitiranje. Aplikacija pokušava ponovno uspostaviti vezu,
+ ako je to moguće. Pričekajte nekoliko sekundi i pokušajte ponovno.
+
+ Izvođenje
+ radnje nije uspjelo
+
+ Sinkroniziranje s
+ uređajem za emitiranje nije uspjelo
+
+
+ Učitavanje medijskog sadržaja na uređaj za emitiranje nije uspjelo
+
+ Traženje
+ nove pozicije na uređaju za emitiranje nije uspjelo
+
+ Došlo je do
+ pogreške poslužitelja u playeru prijemnika
+
+
+ Autorizacija je istekla
+
+ Ažuriranje stila titlova
+ nije uspjelo.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-id/strings.xml b/CCL/src/main/res/values-id/strings.xml
new file mode 100755
index 000000000..e549f6cf3
--- /dev/null
+++ b/CCL/src/main/res/values-id/strings.xml
@@ -0,0 +1,97 @@
+
+ Pustaka Pendamping
+ Cast
+
+ 1.10
+
+ Putar di...
+
+ Langsung
+ Batal
+ Aktif
+ Nonaktif
+
+ Informasi Tidak Tersedia
+
+ Oke
+ Kesalahan
+
+ Mentransmisikan ke %1$s
+
+ Memuat…
+
+ Informasi media tidak tersedia
+
+ Mencoba memulihkan
+ sesi sebelumnya...
+
+
+ Gagal meluncurkan aplikasi
+
+
+ Waktu permintaan untuk meluncurkan aplikasi telah habis!
+
+
+ Aplikasi yang ingin dicoba diluncurkan tidak tersedia pada perangkat Chromecast Anda.
+
+
+ Gagal memulai pemutaran media
+
+
+ Gagal menghentikan pemutaran media
+
+
+ Gagal menjeda pemutaran media
+
+ Terjadi
+ kesalahan yang tidak dikenal
+
+
+ Tidak dapat tersambung ke perangkat
+
+ Gagal menetapkan
+ volume
+
+ Tidak ada
+ sambungan ke perangkat transmisi
+
+ Tidak ada sambungan
+
+
+ Sambungan ke perangkat transmisi terputus. Aplikasi sedang mencoba menyambungkan kembali,
+ jika memungkinkan. Tunggu beberapa saat dan coba lagi.
+
+ Gagal
+ melakukan tindakan
+
+ Gagal menyinkronkan
+ dengan perangkat transmisi
+
+
+ Gagal memuat media pada perangkat transmisi
+
+ Gagal
+ mencari posisi baru pada perangkat transmisi
+
+ Pemutar penerima
+ mengalami kesalahan sever
+
+
+ Waktu otorisasi telah habis
+
+ Gagal memperbarui gaya
+ Teks.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-in/strings.xml b/CCL/src/main/res/values-in/strings.xml
new file mode 100755
index 000000000..e549f6cf3
--- /dev/null
+++ b/CCL/src/main/res/values-in/strings.xml
@@ -0,0 +1,97 @@
+
+ Pustaka Pendamping
+ Cast
+
+ 1.10
+
+ Putar di...
+
+ Langsung
+ Batal
+ Aktif
+ Nonaktif
+
+ Informasi Tidak Tersedia
+
+ Oke
+ Kesalahan
+
+ Mentransmisikan ke %1$s
+
+ Memuat…
+
+ Informasi media tidak tersedia
+
+ Mencoba memulihkan
+ sesi sebelumnya...
+
+
+ Gagal meluncurkan aplikasi
+
+
+ Waktu permintaan untuk meluncurkan aplikasi telah habis!
+
+
+ Aplikasi yang ingin dicoba diluncurkan tidak tersedia pada perangkat Chromecast Anda.
+
+
+ Gagal memulai pemutaran media
+
+
+ Gagal menghentikan pemutaran media
+
+
+ Gagal menjeda pemutaran media
+
+ Terjadi
+ kesalahan yang tidak dikenal
+
+
+ Tidak dapat tersambung ke perangkat
+
+ Gagal menetapkan
+ volume
+
+ Tidak ada
+ sambungan ke perangkat transmisi
+
+ Tidak ada sambungan
+
+
+ Sambungan ke perangkat transmisi terputus. Aplikasi sedang mencoba menyambungkan kembali,
+ jika memungkinkan. Tunggu beberapa saat dan coba lagi.
+
+ Gagal
+ melakukan tindakan
+
+ Gagal menyinkronkan
+ dengan perangkat transmisi
+
+
+ Gagal memuat media pada perangkat transmisi
+
+ Gagal
+ mencari posisi baru pada perangkat transmisi
+
+ Pemutar penerima
+ mengalami kesalahan sever
+
+
+ Waktu otorisasi telah habis
+
+ Gagal memperbarui gaya
+ Teks.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-it/strings.xml b/CCL/src/main/res/values-it/strings.xml
new file mode 100755
index 000000000..a7d761d8f
--- /dev/null
+++ b/CCL/src/main/res/values-it/strings.xml
@@ -0,0 +1,100 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Riproduci
+ su…
+
+ Live
+ Annulla
+ Attivo
+ Non attivo
+
+ Informazione non disponibile
+
+ OK
+ Errore
+ Trasmissione
+ su %1$s in corso
+
+ Caricamento in corso…
+
+
+ Nessuna informazione disponibile sul file multimediale
+
+ Tentativo di
+ recupero della sessione precedente in corso...
+
+
+ Impossibile avviare l\'applicazione
+
+ La
+ richiesta di avvio dell\'applicazione è scaduta.
+
+
+ L\'applicazione che stai tentando di avviare non è disponibile sul tuo dispositivo
+ Chromecast
+
+
+ Impossibile avviare la riproduzione del file multimediale
+
+
+ Impossibile interrompere la riproduzione del file multimediale
+
+
+ Impossibile mettere in pausa la riproduzione del file multimediale
+
+ Si è
+ verificato un errore sconosciuto
+
+
+ Connessione al dispositivo non riuscita
+
+ Impossibile
+ impostare il volume
+
+ Non è presente
+ alcuna connessione al dispositivo di trasmissione
+
+ Nessuna connessione
+
+
+ La connessione al dispositivo di trasmissione è stata persa. L\'applicazione sta tentando di
+ ristabilire la connessione, se possibile. Attendi qualche secondo e riprova.
+
+ Impossibile
+ eseguire l\'azione
+
+ Impossibile eseguire la
+ sincronizzazione con il dispositivo di trasmissione
+
+
+ Impossibile caricare il file multimediale sul dispositivo di trasmissione
+
+
+ Impossibile cercare la nuova posizione sul dispositivo di trasmissione
+
+ Il giocatore
+ ricevente ha riscontrato un errore del server
+
+
+ Autorizzazione scaduta
+
+ Impossibile caricare lo
+ stile dei sottotitoli.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ja/strings.xml b/CCL/src/main/res/values-ja/strings.xml
new file mode 100755
index 000000000..b80aa3339
--- /dev/null
+++ b/CCL/src/main/res/values-ja/strings.xml
@@ -0,0 +1,92 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ 次の端末で再生…
+
+ ライブ
+ キャンセル
+ オン
+ オフ
+
+ ご利用可能な情報はありません
+
+ OK
+ エラー
+
+ %1$sにキャストしています
+
+ 読み込んでいます…
+
+ ご利用可能なメディア情報はありません
+
+
+ 前回のセッションを復元しようとしています…
+
+
+ アプリの起動に失敗しました
+
+
+ アプリの起動リクエストがタイムアウトになりました。
+
+
+ 起動しようとしているアプリはChromecastデバイスではご利用いただけません
+
+
+ メディアの再生の開始に失敗しました
+
+
+ メディアの再生の停止に失敗しました
+
+
+ メディアの再生の一時停止に失敗しました
+
+
+ 不明なエラーが発生しました
+
+
+ デバイスに接続できませんでした
+
+ 音量の設定に失敗しました
+
+
+ キャストデバイスへの接続が検出されません
+
+ 接続なし
+
+
+ キャストデバイスへの接続が切断されました。アプリは可能な限り接続の再確立を試みます。数秒ほどお待ちになってからもう一度お試しください。
+
+ 操作の実行に失敗しました
+
+ キャストデバイスとの同期に失敗しました
+
+
+ キャストデバイス上のメディアの読み込みに失敗しました
+
+
+ キャストデバイス上で新しい位置が見つかりませんでした
+
+
+ レシーバプレーヤーの稼働中にサーバーエラーが発生しました
+
+
+ 承認がタイムアウトになりました
+
+ 字幕スタイルの更新に失敗しました
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ko/strings.xml b/CCL/src/main/res/values-ko/strings.xml
new file mode 100755
index 000000000..5e3c20e23
--- /dev/null
+++ b/CCL/src/main/res/values-ko/strings.xml
@@ -0,0 +1,2 @@
+
+2.6 재생할 기기... 실시간 취소 사용 사용 안함 정보 사용 불가 일시중지 재생 연결 해제 없음 확인 오류 %1$s(으)로 전송 로드 중... 미디어 정보가 없습니다. 애플리케이션을 실행하지 못했습니다. 애플리케이션 실행 요청 제한 시간이 초과되었습니다. 실행하려는 애플리케이션은 전송 기기에서 지원되지 않습니다. 미디어 재생을 시작하지 못했습니다. 미디어 재생을 중지하지 못했습니다. 미디어 재생을 일시중지하지 못했습니다. 기기에 연결할 수 없습니다. 볼륨을 설정하지 못했습니다. 전송 기기에 연결되지 않았습니다. 연결되지 않았습니다. 전송 기기와의 연결이 끊어졌습니다. 애플리케이션에서 가능하면 다시 연결하려고 시도 중입니다. 잠시 기다린 후 다시 시도해 주세요. 작업을 수행하지 못했습니다. 전송 기기와 동기화하지 못했습니다. 전송 기기에서 새 위치를 찾지 못했습니다. 수신기 플레이어에서 심각한 오류가 발생했습니다. 승인 제한 시간이 초과되었습니다. 자막 스타일을 업데이트하지 못했습니다. 다음 콘텐츠
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ln/strings.xml b/CCL/src/main/res/values-ln/strings.xml
new file mode 100755
index 000000000..92d8666cc
--- /dev/null
+++ b/CCL/src/main/res/values-ln/strings.xml
@@ -0,0 +1,99 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Lire sur…
+
+ Disponible en ligne
+ Annuler
+ Activer
+ Désactiver
+
+ Informations non disponibles.
+
+ OK
+ Erreur
+ Cast vers %1$s
+ en cours...
+
+ Chargement en cours…
+
+
+ Aucune information sur le média disponible.
+
+ Tentative de
+ récupération de la session précédente en cours…
+
+
+ Impossible de lancer l\'application.
+
+ La
+ demande de lancement de l\'application a expiré.
+
+
+ L\'application que vous essayez de lancer n\'est pas disponible sur votre appareil
+ Chromecast.
+
+
+ Impossible de lancer la lecture du média.
+
+
+ Impossible d\'arrêter la lecture du média.
+
+
+ Impossible de mettre sur pause la lecture du média.
+
+ Une
+ erreur inconnue a été détectée.
+
+
+ Connexion impossible à l\'appareil.
+
+ Impossible de
+ régler le volume.
+
+ Aucune connexion à
+ l\'appareil Cast n\'est disponible.
+
+ Aucune connexion.
+
+
+ La connexion à l\'appareil Cast a été perdue. L\'application essaye à nouveau d\'établir une
+ connexion, si possible. Veuillez patienter quelques secondes et réessayer.
+
+ Impossible
+ d\'effectuer l\'action.
+
+ Impossible d\'effectuer
+ la synchronisation avec l\'appareil Cast.
+
+
+ Impossible de charger le média sur l\'appareil Cast.
+
+
+ Impossible de rechercher la nouvelle position sur l\'appareil Cast.
+
+ Le lecteur de
+ l\'appareil de réception a rencontré une erreur de serveur.
+
+
+ Expiration de l\'autorisation
+
+ Impossible de mettre à
+ jour le style des sous-titres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-lt/strings.xml b/CCL/src/main/res/values-lt/strings.xml
new file mode 100755
index 000000000..5427cd020
--- /dev/null
+++ b/CCL/src/main/res/values-lt/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Leisti…
+
+ Aktyvus
+ Atšaukti
+ Įjungta
+ Išjungta
+
+ Informacija nepasiekiama
+
+ Gerai
+ Klaida
+ Perduodama į
+ %1$s
+
+ Įkeliama…
+
+ Medijos informacija nepasiekiama
+
+ Bandoma atkurti
+ ankstesnį seansą…
+
+
+ Nepavyko paleisti programos
+
+
+ Baigėsi programos paleidimo užklausos skirtasis laikas!
+
+
+ Programa, kurią bandote paleisti, nepasiekiama jūsų „Chromecast“ įrenginyje
+
+
+ Nepavyko pradėti medijos atkūrimo
+
+
+ Nepavyko sustabdyti medijos atkūrimo
+
+
+ Nepavyko pristabdyti medijos atkūrimo
+
+ Aptikta
+ nežinoma klaida
+
+
+ Nepavyko prisijungti prie įrenginio
+
+ Nepavyko nustatyti
+ garsumo
+
+ Nėra ryšio su
+ perdavimo įrenginiu
+
+ Nėra ryšio
+
+
+ Prarastas ryšys su perdavimo įrenginiu. Programa bando iš naujo užmegzti ryšį, jei pavyks.
+ Šiek tiek palaukite ir bandykite dar kartą.
+
+ Nepavyko
+ atlikti veiksmo
+
+ Nepavyko sinchronizuoti
+ su perdavimo įrenginiu
+
+
+ Nepavyko įkelti medijos perdavimo įrenginyje
+
+ Nepavyko
+ surasti naujos pozicijos perdavimo įrenginyje
+
+ Gavėjo
+ leistuvėje pateikta serverio klaida
+
+
+ Baigėsi autorizavimo skirtasis laikas
+
+ Nepavyko atnaujinti
+ antraščių stiliaus.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-lv/strings.xml b/CCL/src/main/res/values-lv/strings.xml
new file mode 100755
index 000000000..d173e4e05
--- /dev/null
+++ b/CCL/src/main/res/values-lv/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Spēlēt...
+
+ Tiešraide
+ Atcelt
+ Ieslēgt
+ Izslēgt
+
+ Informācija nav pieejama.
+
+ Labi
+ Kļūda
+ Notiek apraide
+ uz: %1$s
+
+ Notiek ielāde…
+
+ Informācija par multivides saturu nav pieejama.
+
+ Tiek mēģināts
+ atjaunot iepriekšējo sesiju...
+
+
+ Neizdevās palaist lietojumprogrammu
+
+
+ Radās lietojumprogrammas palaišanas pieprasījuma noildze!
+
+
+ Lietojumprogramma, kuru mēģināt palaist, nav pieejama jūsu Chromecast ierīcē.
+
+
+ Neizdevās sākt multivides satura atskaņošanu.
+
+
+ Neizdevās pārtraukt multivides satura atskaņošanu.
+
+
+ Neizdevās apturēt multivides satura atskaņošanu.
+
+ Radās
+ nezināma kļūda.
+
+
+ Nevarēja izveidot savienojumu ar ierīci.
+
+ Neizdevās iestatīt
+ skaļumu.
+
+ Nav izveidots
+ savienojums ar Cast ierīci.
+
+ Nav savienojuma.
+
+
+ Tika pārtraukts savienojums ar Cast ierīci. Tiek mēģināts atkārtoti izveidot savienojumu ar
+ lietojumprogrammu (ja tas ir iespējams). Lūdzu, dažas sekundes uzgaidiet un pēc tam mēģiniet
+ vēlreiz.
+
+ Neizdevās
+ veikt darbību.
+
+ Neizdevās veikt
+ sinhronizāciju ar Cast ierīci.
+
+
+ Neizdevās ielādēt multivides saturu Cast ierīcē.
+
+
+ Neizdevās atrast jaunu pozīciju Cast ierīcē.
+
+ Saņēmēja pusē
+ radās servera kļūda.
+
+
+ Iestājās autorizācijas noildze.
+
+ Neizdevās atjaunināt
+ parakstu stilu.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ml/strings.xml b/CCL/src/main/res/values-ml/strings.xml
new file mode 100755
index 000000000..74da86659
--- /dev/null
+++ b/CCL/src/main/res/values-ml/strings.xml
@@ -0,0 +1,98 @@
+
+ കാസ്റ്റ് സഹായ ലൈബ്രറി
+
+ 1.10
+
+ പ്ലേ
+ ചെയ്തുകൊണ്ടേയിരിക്കുക...
+
+ തത്സമയം
+ റദ്ദാക്കുക
+ ഓൺ ചെയ്യുക
+ ഓഫ് ചെയ്യുക
+
+ വിവരം ലഭ്യമല്ല
+
+ ശരി
+ പിശക്
+ %1$s-ലേക്ക്
+ കാസ്റ്റുചെയ്യുന്നു
+
+ ലോഡുചെയ്യുന്നു...
+
+
+ മീഡിയ വിവരങ്ങളൊന്നും ലഭ്യമല്ല
+
+ മുമ്പത്തെ സെഷൻ
+ വീണ്ടെടുക്കാൻ ശ്രമിക്കുന്നു…
+
+
+ ആപ്ലിക്കേഷൻ സമാരംഭിക്കുന്നത് പരാജയപ്പെട്ടു
+
+
+ ആപ്ലിക്കേഷൻ സമാരംഭിക്കുന്നതിനുള്ള അഭ്യർത്ഥനാ സമയം കഴിഞ്ഞു!
+
+
+ നിങ്ങൾ സമാരംഭിക്കാൻ ശ്രമിക്കുന്ന ആപ്ലിക്കേഷൻ നിങ്ങളുടെ Chromecast ഉപകരണത്തിൽ ലഭ്യമല്ല
+
+
+ മീഡിയ പ്ലേബാക്ക് ആരംഭിക്കുന്നത് പരാജയപ്പെട്ടു
+
+
+ മീഡിയ പ്ലേബാക്ക് നിർത്തുന്നത് പരാജയപ്പെട്ടു
+
+
+ മീഡിയ പ്ലേബാക്ക് താൽക്കാലം നിർത്തുന്നത് പരാജയപ്പെട്ടു
+
+ ഒരു
+ അജ്ഞാത പിശക് സംഭവിച്ചു
+
+
+ ഉപകരണത്തിലേക്ക് ബന്ധിപ്പിക്കാൻ കഴിഞ്ഞില്ല
+
+ വോളിയം
+ ക്രമീകരിക്കുന്നത് പരാജയപ്പെട്ടു
+
+ കാസ്റ്റ്
+ ഉപകരണത്തിലേക്ക് നിലവിൽ കണക്ഷനൊന്നുമില്ല
+
+ കണക്ഷനൊന്നുമില്ല
+
+
+ കാസ്റ്റ് ഉപകരണത്തിലേക്കുള്ള കണക്ഷൻ നഷ്ടമായി. സാധ്യമെങ്കിൽ, കണക്ഷൻ പുനഃസ്ഥാപിക്കുന്നതിന്
+ ആപ്ലിക്കേഷൻ ശ്രമിക്കുന്നു. അൽപ്പസമയം കാത്തിരുന്ന ശേഷം വീണ്ടും ശ്രമിക്കുക.
+
+ നടപടി
+ നിർവഹിക്കുന്നത് പരാജയപ്പെട്ടു
+
+ കാസ്റ്റ് ഉപകരണവുമായി
+ സമന്വയിപ്പിക്കുന്നത് പരാജയപ്പെട്ടു
+
+
+ കാസ്റ്റ് ഉപകരണത്തിൽ മീഡിയ ലോഡുചെയ്യുന്നത് പരാജയപ്പെട്ടു
+
+ കാസ്റ്റ്
+ ഉപകരണത്തിൽ പുതിയ സ്ഥാനം തേടൽ പരാജയപ്പെട്ടു
+
+ റിസീവർ പ്ലെയർ
+ ഒരു സെർവർ പിശക് നേരിട്ടു
+
+
+ ആധികാരികമാക്കൽ സമയം കഴിഞ്ഞു
+
+ ക്യാപ്ഷൻ ശൈലി അപ്ഡേറ്റ്
+ ചെയ്യുന്നത് പരാജയപ്പെട്ടു.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-mo/strings.xml b/CCL/src/main/res/values-mo/strings.xml
new file mode 100755
index 000000000..e9153a23a
--- /dev/null
+++ b/CCL/src/main/res/values-mo/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Redați pe…
+
+ Live
+ Anulați
+ Activați
+ Dezactivați
+
+ Informația nu este disponibilă
+
+ OK
+ Eroare
+ Se proiectează
+ pe %1$s
+
+ Se încarcă…
+ Nu
+ sunt disponibile informații media
+
+ Se încearcă
+ recuperarea sesiunii anterioare…
+
+
+ Eroare la lansarea aplicației
+
+
+ Solicitarea de lansare a aplicației a expirat!
+
+
+ Aplicația pe care încercați să o lansați nu este disponibilă pe Chromecast
+
+
+ Eroare la inițierea redării conținutului media
+
+
+ Eroare la oprirea redării conținutului media
+
+
+ Eroare la întreruperea redării conținutului media
+
+ Eroare
+ necunoscută
+
+
+ Nu s-a putut conecta la dispozitiv
+
+ Eroare la setarea
+ volumului
+
+ Nu există nicio
+ conexiune la dispozitivul de proiecție
+
+ Nicio conexiune
+
+
+ S-a pierdut conexiunea la dispozitivul de proiecție. Aplicația încearcă să restabilească
+ conexiunea, dacă este posibil. Așteptați câteva secunde și încercați din nou.
+
+ Eroare la
+ realizarea acțiunii
+
+ Eroare la sincronizarea
+ cu dispozitivul de proiecție
+
+
+ Eroare la încărcarea conținutului media pe dispozitivul de proiecție
+
+ Eroare
+ la navigarea la noua poziție pe dispozitivul de proiecție
+
+ Playerul
+ receiverului a întâmpinat o eroare de server
+
+
+ Autorizarea a expirat
+
+ Eroare la actualizarea
+ stilului subtitrărilor.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-nb/strings.xml b/CCL/src/main/res/values-nb/strings.xml
new file mode 100755
index 000000000..1c30710ca
--- /dev/null
+++ b/CCL/src/main/res/values-nb/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Spill på
+
+ Direkte
+ Avbryt
+ På
+ Av
+
+ Informasjonen er ikke tilgjengelig
+
+ OK
+ Feil
+ Sender til
+ %1$s
+
+ Laster inn …
+
+ Ingen medieinformasjon er tilgjengelig
+
+ Forsøker å
+ gjenopprette den forrige økten …
+
+
+ Appen kunne ikke startes
+
+
+ Forespørselen om å kjøre appen ble tidsavbrutt.
+
+
+ Appen du prøver å kjøre, er ikke tilgjengelig på Chromecast-enheten din
+
+
+ Medieavspillingen mislyktes
+
+
+ Medieavspillingen kunne ikke stoppes
+
+
+ Medieavspillingen kunne ikke settes på pause
+
+ Det har
+ oppstått en ukjent feil
+
+
+ Kan ikke koble til enheten
+
+ Kan ikke stille
+ inn volumet
+
+ Det finnes ingen
+ tilkobling til Cast-enheten
+
+ Ingen tilkobling
+
+
+ Tilkoblingen til Cast-enheten er tapt. Appen prøver om mulig å gjenopprette tilkoblingen.
+ Vent noen sekunder, og prøv på nytt.
+
+ Handlingen
+ kunne ikke gjennomføres
+
+ Synkroniseringen med
+ Cast-enheten mislyktes
+
+
+ Mediene på Cast-enheten kunne ikke lastes inn
+
+ Søkingen
+ etter ny posisjon på Cast-enheten mislyktes
+
+ Mottakeren har
+ støtt på en tjenerfeil
+
+
+ Godkjenningen er utløpt
+
+ Stilen for tekstingen
+ kunne ikke oppdateres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-no/strings.xml b/CCL/src/main/res/values-no/strings.xml
new file mode 100755
index 000000000..1c30710ca
--- /dev/null
+++ b/CCL/src/main/res/values-no/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Spill på
+
+ Direkte
+ Avbryt
+ På
+ Av
+
+ Informasjonen er ikke tilgjengelig
+
+ OK
+ Feil
+ Sender til
+ %1$s
+
+ Laster inn …
+
+ Ingen medieinformasjon er tilgjengelig
+
+ Forsøker å
+ gjenopprette den forrige økten …
+
+
+ Appen kunne ikke startes
+
+
+ Forespørselen om å kjøre appen ble tidsavbrutt.
+
+
+ Appen du prøver å kjøre, er ikke tilgjengelig på Chromecast-enheten din
+
+
+ Medieavspillingen mislyktes
+
+
+ Medieavspillingen kunne ikke stoppes
+
+
+ Medieavspillingen kunne ikke settes på pause
+
+ Det har
+ oppstått en ukjent feil
+
+
+ Kan ikke koble til enheten
+
+ Kan ikke stille
+ inn volumet
+
+ Det finnes ingen
+ tilkobling til Cast-enheten
+
+ Ingen tilkobling
+
+
+ Tilkoblingen til Cast-enheten er tapt. Appen prøver om mulig å gjenopprette tilkoblingen.
+ Vent noen sekunder, og prøv på nytt.
+
+ Handlingen
+ kunne ikke gjennomføres
+
+ Synkroniseringen med
+ Cast-enheten mislyktes
+
+
+ Mediene på Cast-enheten kunne ikke lastes inn
+
+ Søkingen
+ etter ny posisjon på Cast-enheten mislyktes
+
+ Mottakeren har
+ støtt på en tjenerfeil
+
+
+ Godkjenningen er utløpt
+
+ Stilen for tekstingen
+ kunne ikke oppdateres.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-pl/strings.xml b/CCL/src/main/res/values-pl/strings.xml
new file mode 100755
index 000000000..caf7939d1
--- /dev/null
+++ b/CCL/src/main/res/values-pl/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Odtwarzaj…
+
+ Na żywo
+ Anuluj
+ Wł.
+ Wył.
+
+ Informacje niedostępne
+
+ OK
+ Błąd
+ Przesyłanie do
+ %1$s
+
+ Wczytywanie…
+ Brak
+ dostępnych informacji o multimediach
+
+ Próba odzyskania
+ poprzedniej sesji…
+
+
+ Nie udało się uruchomić aplikacji
+
+
+ Upłynął czas żądania uruchomienia aplikacji
+
+
+ Aplikacja, którą próbujesz uruchomić, jest niedostępna na Twoim urządzeniu Chromecast
+
+
+ Nie udało się uruchomić odtwarzania multimediów
+
+ Nie
+ udało się zatrzymać odtwarzania multimediów
+
+
+ Nie udało się wstrzymać odtwarzania multimediów
+
+
+ Wystąpił nieznany błąd
+
+
+ Nie udało się połączyć z urządzeniem
+
+ Nie udało się
+ ustawić głośności
+
+ Brak połączenia z
+ urządzeniem przesyłającym
+
+ Brak połączenia
+
+
+ Utracono połączenie z urządzeniem przesyłającym. Aplikacja podejmuje próby ponownego
+ nawiązania połączenia. Poczekaj kilka sekund i spróbuj ponownie.
+
+ Nie udało się
+ wykonać działania
+
+ Nie udało się
+ zsynchronizować z urządzeniem przesyłającym
+
+ Nie
+ udało się wczytać multimediów na urządzenie przesyłające
+
+ Nie
+ udało się przejść do nowej pozycji w urządzeniu przesyłającym
+
+ W odtwarzaczu
+ odbiornika wystąpił błąd serwera
+
+
+ Upłynął limit czasu autoryzacji
+
+ Nie udało się
+ zaktualizować stylu Napisów.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-pt-rBR/strings.xml b/CCL/src/main/res/values-pt-rBR/strings.xml
new file mode 100755
index 000000000..259036738
--- /dev/null
+++ b/CCL/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Jogar em…
+
+ Ao vivo
+ Cancelar
+ Ativar
+ Desativar
+
+ Informação indisponível
+
+ OK
+ Erro
+ Transmitindo
+ para %1$s
+
+ Carregando…
+
+ Nenhuma informação de mídia disponível
+
+ Tentando recuperar
+ sessão anterior...
+
+
+ Falha ao iniciar o aplicativo
+
+ A
+ solicitação para iniciar o aplicativo expirou.
+
+
+ O aplicativo que você está tentando iniciar não está disponível no seu dispositivo
+ Chromecast
+
+
+ Falha ao iniciar a reprodução de mídia
+
+
+ Falha ao interromper a reprodução de mídia
+
+
+ Falha ao pausar a reprodução de mídia
+
+ Um erro
+ desconhecido foi encontrado
+
+
+ Não foi possível conectar ao dispositivo
+
+ Falha ao definir o
+ volume
+
+ Sem conexão com o
+ dispositivo de transmissão
+
+ Sem conexão
+
+
+ A conexão com o dispositivo de transmissão foi perdida. O aplicativo está tentando
+ restabelecer a conexão, se possível. Aguarde alguns segundos e tente novamente.
+
+ Falha ao
+ executar a ação
+
+ Falha ao sincronizar
+ com o dispositivo de transmissão
+
+
+ Falha ao carregar a mídia no dispositivo de transmissão
+
+ Falha ao
+ buscar a nova posição no dispositivo de transmissão
+
+ O player de
+ destino encontrou um erro de servidor
+
+ A
+ autorização expirou
+
+ Falha ao atualizar o
+ estilo das legendas.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-pt-rPT/strings.xml b/CCL/src/main/res/values-pt-rPT/strings.xml
new file mode 100755
index 000000000..093bf970a
--- /dev/null
+++ b/CCL/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Reproduzir
+ em…
+
+ Em direto
+ Cancelar
+ Ligar
+ Desligar
+
+ Informações não disponíveis
+
+ OK
+ Erro
+ A transmitir
+ para %1$s
+
+ A carregar…
+
+ Informações sobre multimédia não disponíveis.
+
+ A tentar recuperar
+ a sessão anterior…
+
+
+ Falha ao iniciar a aplicação.
+
+ O
+ pedido de início da aplicação expirou!
+
+
+ A aplicação que está a tentar iniciar não está disponível no seu dispositivo Chromecast.
+
+
+ Falha ao iniciar a reprodução de multimédia.
+
+
+ Falha ao parar a reprodução de multimédia.
+
+
+ Falha ao colocar a reprodução de multimédia em pausa.
+
+ Ocorreu
+ um erro desconhecido.
+
+
+ Não foi possível ligar ao dispositivo.
+
+ Falha ao definir o
+ volume.
+
+ Não existe ligação
+ ao dispositivo de transmissão.
+
+ Sem ligação
+
+
+ Ligação ao dispositivo de transmissão perdida. A aplicação está a tentar restabelecer a
+ ligação, se possível. Aguarde alguns segundos e tente de novo.
+
+ Falha ao
+ efetuar a ação.
+
+ Falha ao sincronizar
+ com o dispositivo de transmissão.
+
+
+ Falha ao carregar multimédia no dispositivo de transmissão.
+
+ Falha ao
+ avançar para a nova posição no dispositivo de transmissão.
+
+ O dispositivo do
+ recetor detetou um erro de servidor.
+
+ A
+ autorização expirou.
+
+ Falha ao atualizar o
+ estilo das legendas.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-pt/strings.xml b/CCL/src/main/res/values-pt/strings.xml
new file mode 100755
index 000000000..259036738
--- /dev/null
+++ b/CCL/src/main/res/values-pt/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Jogar em…
+
+ Ao vivo
+ Cancelar
+ Ativar
+ Desativar
+
+ Informação indisponível
+
+ OK
+ Erro
+ Transmitindo
+ para %1$s
+
+ Carregando…
+
+ Nenhuma informação de mídia disponível
+
+ Tentando recuperar
+ sessão anterior...
+
+
+ Falha ao iniciar o aplicativo
+
+ A
+ solicitação para iniciar o aplicativo expirou.
+
+
+ O aplicativo que você está tentando iniciar não está disponível no seu dispositivo
+ Chromecast
+
+
+ Falha ao iniciar a reprodução de mídia
+
+
+ Falha ao interromper a reprodução de mídia
+
+
+ Falha ao pausar a reprodução de mídia
+
+ Um erro
+ desconhecido foi encontrado
+
+
+ Não foi possível conectar ao dispositivo
+
+ Falha ao definir o
+ volume
+
+ Sem conexão com o
+ dispositivo de transmissão
+
+ Sem conexão
+
+
+ A conexão com o dispositivo de transmissão foi perdida. O aplicativo está tentando
+ restabelecer a conexão, se possível. Aguarde alguns segundos e tente novamente.
+
+ Falha ao
+ executar a ação
+
+ Falha ao sincronizar
+ com o dispositivo de transmissão
+
+
+ Falha ao carregar a mídia no dispositivo de transmissão
+
+ Falha ao
+ buscar a nova posição no dispositivo de transmissão
+
+ O player de
+ destino encontrou um erro de servidor
+
+ A
+ autorização expirou
+
+ Falha ao atualizar o
+ estilo das legendas.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ro/strings.xml b/CCL/src/main/res/values-ro/strings.xml
new file mode 100755
index 000000000..e9153a23a
--- /dev/null
+++ b/CCL/src/main/res/values-ro/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Redați pe…
+
+ Live
+ Anulați
+ Activați
+ Dezactivați
+
+ Informația nu este disponibilă
+
+ OK
+ Eroare
+ Se proiectează
+ pe %1$s
+
+ Se încarcă…
+ Nu
+ sunt disponibile informații media
+
+ Se încearcă
+ recuperarea sesiunii anterioare…
+
+
+ Eroare la lansarea aplicației
+
+
+ Solicitarea de lansare a aplicației a expirat!
+
+
+ Aplicația pe care încercați să o lansați nu este disponibilă pe Chromecast
+
+
+ Eroare la inițierea redării conținutului media
+
+
+ Eroare la oprirea redării conținutului media
+
+
+ Eroare la întreruperea redării conținutului media
+
+ Eroare
+ necunoscută
+
+
+ Nu s-a putut conecta la dispozitiv
+
+ Eroare la setarea
+ volumului
+
+ Nu există nicio
+ conexiune la dispozitivul de proiecție
+
+ Nicio conexiune
+
+
+ S-a pierdut conexiunea la dispozitivul de proiecție. Aplicația încearcă să restabilească
+ conexiunea, dacă este posibil. Așteptați câteva secunde și încercați din nou.
+
+ Eroare la
+ realizarea acțiunii
+
+ Eroare la sincronizarea
+ cu dispozitivul de proiecție
+
+
+ Eroare la încărcarea conținutului media pe dispozitivul de proiecție
+
+ Eroare
+ la navigarea la noua poziție pe dispozitivul de proiecție
+
+ Playerul
+ receiverului a întâmpinat o eroare de server
+
+
+ Autorizarea a expirat
+
+ Eroare la actualizarea
+ stilului subtitrărilor.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ru/strings.xml b/CCL/src/main/res/values-ru/strings.xml
new file mode 100755
index 000000000..3d3c8ba72
--- /dev/null
+++ b/CCL/src/main/res/values-ru/strings.xml
@@ -0,0 +1,2 @@
+
+2.6 Смотреть на устройстве... Прямой эфир Отмена Включить Выключить Информация отсутствует. Приостановить Воспроизвести Отключить Нет ОК Ошибка Передача на устройство \"%1$s\" Загрузка… Сведения о контенте отсутствуют. Не удалось запустить приложение. Время, отведенное на запуск приложения, истекло. Приложение недоступно на этом устройстве. Не удалось начать воспроизведение. Не удалось остановить воспроизведение. Не удалось приостановить воспроизведение. Не удалось подключиться к устройству. Не удалось настроить громкость. Нет соединения с устройством. Нет соединения. Соединение прервано. Приложение пытается его восстановить. Подождите несколько секунд и попробуйте подключиться снова. Не удалось выполнить действие. Не удалось синхронизировать данные с устройством. Не удалось перейти к новому месту. У проигрывателя устройства возникла серьезная ошибка. Время, отведенное на авторизацию, истекло. Не удалось изменить стиль субтитров. След.
\ No newline at end of file
diff --git a/CCL/src/main/res/values-sl/strings.xml b/CCL/src/main/res/values-sl/strings.xml
new file mode 100755
index 000000000..89fa9173d
--- /dev/null
+++ b/CCL/src/main/res/values-sl/strings.xml
@@ -0,0 +1,98 @@
+
+ Knjižnica Cast
+ Companion
+
+ 1.10
+
+ Predvajaj v
+ …
+
+ V živo
+ Prekliči
+ Vklopljeno
+ Izklopljeno
+
+ Informacije niso na voljo
+
+ V redu
+ Napaka
+ Predvajanje v
+ %1$s
+
+ Nalaganje …
+
+ Podatki o medijih niso na voljo
+
+ Poskus obnovitve
+ prejšnje seje …
+
+
+ Zagon aplikacije ni bil uspešno izveden
+
+
+ Zahteva za zagon aplikacije je potekla.
+
+
+ Aplikacija, ki jo želite zagnati, ni na voljo v napravi Chromecast
+
+
+ Predvajanje medijev se ni uspešno začelo.
+
+
+ Predvajanje medijev ni bilo uspešno končano
+
+
+ Predvajanje medijev ni bilo uspešno zaustavljeno
+
+ Najdena
+ je bila neznana napaka
+
+
+ Povezave z napravo ni bilo mogoče vzpostaviti
+
+ Nastavitev
+ glasnosti ni uspela
+
+ Z napravo za
+ predvajanje ni vzpostavljene povezave
+
+ Ni povezave
+
+
+ Povezava z napravo za predvajanje je bila prekinjena. Aplikacija bo poskusila znova
+ vzpostaviti povezavo. Počakajte nekaj sekund, nato poskusite znova.
+
+ Dejanje ni
+ bilo uspešno izvedeno
+
+ Sinhronizacija z
+ napravo za predvajanje ni bila uspešno izvedena
+
+
+ Mediji niso bili uspešno naloženi v napravo za predvajanje
+
+ Iskanje
+ novega položaja v napravi za predvajanje ni bilo uspešno
+
+ Predvajalnik
+ sprejemnika je naletel na resno napako
+
+
+ Pooblastilo je poteklo
+
+ Slog napisov ni bil
+ uspešno posodobljen.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-sv/strings.xml b/CCL/src/main/res/values-sv/strings.xml
new file mode 100755
index 000000000..5cbc66d49
--- /dev/null
+++ b/CCL/src/main/res/values-sv/strings.xml
@@ -0,0 +1,98 @@
+
+ Kompletterande
+ Cast-bibliotek
+
+ 1.10
+
+ Fortsätt
+ spela …
+
+ Live
+ Avbryt
+ På
+ Av
+
+ Informationen är inte tillgänglig
+
+ OK
+ Fel
+ Castar via
+ %1$s
+
+ Läser in …
+
+ Ingen medieinformation tillgänglig
+
+ Försöker återuppta
+ föregående session …
+
+
+ Det gick inte att starta programmet
+
+
+ Begäran om att starta programmet tog för lång tid!
+
+
+ Programmet du försöker starta finns inte på din Chromecast-enhet
+
+
+ Det gick inte att starta uppspelning av media
+
+ Det
+ gick inte att stoppa uppspelning av media
+
+
+ Det gick inte att pausa uppspelning av media
+
+ Ett
+ okänt fel inträffade
+
+
+ Det gick inte att ansluta till enheten
+
+ Det gick inte att
+ ställa in volymen
+
+ Det finns ingen
+ anslutning till Cast-enheten
+
+ Ingen anslutning
+
+
+ Anslutningen till Cast-enheten avbröts. Programmet försöker återupprätta anslutningen om det
+ är möjligt. Vänta några sekunder och försök sedan igen.
+
+ Det gick inte
+ att utföra åtgärden
+
+ Det gick inte att
+ synkronisera med den Cast-enheten
+
+ Det
+ gick inte att läsa in media på Cast-enheten
+
+ Det gick
+ inte att ställa in den nya positionen på Cast-enheten
+
+ Ett serverfel
+ har inträffat på mottagarspelaren
+
+
+ Auktoriseringen tog för lång tid
+
+ Det gick inte att
+ uppdatera textformatet.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-sw600dp-land/dimens.xml b/CCL/src/main/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 000000000..5fc0c2b30
--- /dev/null
+++ b/CCL/src/main/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ 500dp
+
+
diff --git a/CCL/src/main/res/values-sw600dp/dimens.xml b/CCL/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000..1e9e044aa
--- /dev/null
+++ b/CCL/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ 15sp
+ 13sp
+ 64dp
+ 64dp
+ 16dp
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-ta/strings.xml b/CCL/src/main/res/values-ta/strings.xml
new file mode 100755
index 000000000..01b3ece26
--- /dev/null
+++ b/CCL/src/main/res/values-ta/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ விளையாடவும்…
+
+ நேரலை
+ ரத்துசெய்
+ இயக்கு
+ முடக்கு
+
+ தகவல் கிடைக்கவில்லை
+
+ சரி
+ பிழை
+ %1$s
+ சாதனத்திற்கு அனுப்புகிறது
+
+ ஏற்றுகிறது…
+
+ மீடியா தகவல் ஏதும் இல்லை
+
+ முந்தைய அமர்வை
+ மீட்டெடுக்க முயற்சிக்கிறது...
+
+
+ பயன்பாட்டைத் தொடங்குவது தோல்வியுற்றது
+
+
+ பயன்பாட்டைத் தொடங்குவதற்கான கோரிக்கை நேரம் முடிந்தது!
+
+
+ நீங்கள் தொடங்க முயற்சிக்கும் பயன்பாடு, உங்கள் Chromecast சாதனத்தில் இல்லை
+
+
+ மீடியாவை இயக்கத் தொடங்குவது தோல்வியுற்றது
+
+
+ மீடியா இயக்கத்தை நிறுத்துவது தோல்வியுற்றது
+
+
+ மீடியா இயக்கத்தை இடைநிறுத்துவது தோல்வியுற்றது
+
+ அறியாத
+ பிழை ஏற்பட்டது
+
+
+ சாதனத்துடன் இணைக்க முடியவில்லை
+
+ ஒலியளவை அமைப்பது
+ தோல்வியுற்றது
+
+ அனுப்பும்
+ சாதனத்துடன் தற்போது எந்த இணைப்பும் இல்லை
+
+ இணைப்பு இல்லை
+
+
+ அனுப்பும் சாதனத்துடனான இணைப்பு துண்டிக்கப்பட்டது. இணைப்பை மீண்டு நிறுவ பயன்பாடு
+ முயற்சிக்கிறது. சில வினாடிகள் காத்திருந்து, மீண்டும் முயற்சிக்கவும்.
+
+ செயலைச்
+ செய்வது தோல்வியுற்றது
+
+ அனுப்பும் சாதனத்துடன்
+ ஒத்திசைப்பது தோல்வியுற்றது
+
+
+ அனுப்பும் சாதனத்தில் மீடியாவை ஏற்றுவது தோல்வியுற்றது
+
+
+ அனுப்பும் சாதனத்தில் புதிய நிலைக்குச் செல்வது தோல்வியுற்றது
+
+ ரிசீவர்
+ பிளேயரில் சேவையகப் பிழை ஏற்பட்டுள்ளது
+
+
+ அங்கீகரிப்பு நேரம் காலாவதி ஆனது
+
+ தலைப்புகள் நடையைப்
+ புதுப்பிப்பது தோல்வியடைந்தது.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-th/strings.xml b/CCL/src/main/res/values-th/strings.xml
new file mode 100755
index 000000000..9986f53fb
--- /dev/null
+++ b/CCL/src/main/res/values-th/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ เล่นบน…
+
+ สด
+ ยกเลิก
+ เปิด
+ ปิด
+
+ ไม่มีข้อมูล
+
+ ตกลง
+ ข้อผิดพลาด
+ ส่งไปที่
+ %1$s
+
+ กำลังโหลด…
+
+ ไม่มีข้อมูลสื่อ
+
+
+ กำลังพยายามกู้คืนเซสชันก่อนหน้านี้…
+
+
+ เปิดแอปพลิเคชันไม่สำเร็จ
+
+
+ คำขอเปิดแอปพลิเคชันหมดเวลา
+
+
+ แอปพลิเคชันที่คุณพยายามเปิดไม่มีให้บริการบนอุปกรณ์ Chromecast ของคุณ
+
+
+ เริ่มเล่นสื่อไม่สำเร็จ
+
+
+ หยุดการเล่นสื่อไม่สำเร็จ
+
+
+ หยุดการเล่นสื่อชั่วคราวไม่สำเร็จ
+
+
+ พบข้อผิดพลาดที่ไม่รู้จัก
+
+
+ ไม่สามารถเชื่อมต่อกับอุปกรณ์
+
+
+ ตั้งระดับเสียงไม่สำเร็จ
+
+
+ ไม่พบการเชื่อมต่อกับเครื่องส่ง
+
+ ไม่มีการเชื่อมต่อ
+
+
+ ขาดการติดต่อกับเครื่องส่ง แอปพลิเคชันกำลังพยายามเชื่อมต่อใหม่เท่าที่ทำได้
+ โปรดรอสักครู่แล้วลองอีกครั้ง
+
+
+ ดำเนินการไม่สำเร็จ
+
+
+ ซิงค์กับเครื่องส่งไม่สำเร็จ
+
+
+ โหลดสื่อบนเครื่องส่งไม่สำเร็จ
+
+
+ หาตำแหน่งใหม่บนเครื่องส่งไม่สำเร็จ
+
+
+ โปรแกรมเล่นของเครื่องรับพบปัญหาด้านเซิร์ฟเวอร์
+
+
+ การตรวจสอบสิทธิ์หมดเวลา
+
+
+ อัปเดตสไตล์ของคำบรรยายภาพไม่สำเร็จ
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-tl/strings.xml b/CCL/src/main/res/values-tl/strings.xml
new file mode 100755
index 000000000..440f7048a
--- /dev/null
+++ b/CCL/src/main/res/values-tl/strings.xml
@@ -0,0 +1,98 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ I-play sa
+ ...
+
+ Live
+ Kanselahin
+ I-on
+ I-off
+
+ Hindi Available ang Impormasyon
+
+ OK
+ Error
+ Nagka-cast sa
+ %1$s
+
+ Naglo-load…
+
+ Walang available na impormasyon ng media
+
+ Sinusubukang
+ i-recover ang nakaraang session...
+
+
+ Hindi nailunsad ang application
+
+
+ Nag-timeout ang kahilingan upang ilunsad ang application!
+
+
+ Hindi available sa iyong Chromecast device ang application na sinusubukan mong ilunsad
+
+
+ Hindi nasimulan ang pag-playback ng media
+
+
+ Hindi nahinto ang pag-playback ng media
+
+
+ Hindi na-pause ang pag-playback ng media
+
+
+ Nagkaroon ng hindi kilalang error
+
+
+ Hindi makakonekta sa device
+
+ Hindi naitakda ang
+ volume
+
+ Walang nakitang
+ koneksyon sa cast device
+
+ Walang koneksyon
+
+
+ Nawala ang koneksyon sa cast device. Sinusubukan ng application na makagawa muli ng
+ koneksyon, kung maaari. Mangyaring maghintay ng ilang segundo at subukang muli.
+
+ Hindi nagawa
+ ang pagkilos
+
+ Hindi nakapag-sync sa
+ cast device
+
+
+ Hindi na-load ang media sa cast device
+
+ Hindi
+ nahanap ang bagong posisyon sa cast device
+
+ Nagkaroon ng
+ error sa server ang receiver player
+
+
+ Nag-time out ang pagpapahintulot
+
+ Hindi na-update ang
+ estilo ng Mga Caption.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-uk/strings.xml b/CCL/src/main/res/values-uk/strings.xml
new file mode 100755
index 000000000..1da6dd35c
--- /dev/null
+++ b/CCL/src/main/res/values-uk/strings.xml
@@ -0,0 +1,98 @@
+
+ Бібліотека Cast
+ Companion
+
+ 1.10
+
+ Відтворити
+ на…
+
+ Наживо
+ Скасувати
+ Увімкнути
+ Вимкнути
+
+ Інформація недоступна
+
+ OK
+ Помилка
+ Трансляція на
+ пристрій %1$s
+
+ Завантаження…
+
+ Інформація про медіа-вміст недоступна
+
+ Триває спроба
+ відновити попередній сеанс…
+
+
+ Не вдалося запустити додаток
+
+ Час
+ очікування запиту на запуск додатка минув.
+
+
+ Додаток, який ви намагаєтеся запустити, недоступний на вашому пристрої Chromecast
+
+ Не
+ вдалося почати відтворення медіа-вмісту
+
+ Не
+ вдалося зупинити відтворення медіа-вмісту
+
+
+ Не вдалося призупинити відтворення медіа-вмісту
+
+ Сталася
+ невідома помилка
+
+
+ Не вдалося підключитися до пристрою
+
+ Не вдалося
+ налаштувати гучність
+
+ Немає з’єднання з
+ пристроєм для трансляції
+
+ Немає з’єднання
+
+
+ З’єднання з пристроєм для трансляції втрачено. Додаток намагається за можливості повторно
+ встановити з’єднання. Зачекайте кілька секунд і повторіть спробу.
+
+ Не вдалося
+ виконати дію
+
+ Не вдалося
+ синхронізувати з пристроєм для трансляції
+
+ Не
+ вдалося завантажити медіа-вміст на пристрій для трансляції
+
+ Не
+ вдалося знайти нову позицію на пристрої для трансляції
+
+ На
+ програвачі-приймачі сталася помилка сервера
+
+
+ Час очікування авторизації минув
+
+ Не вдалось оновити стиль
+ субтитрів.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-v11/styles.xml b/CCL/src/main/res/values-v11/styles.xml
new file mode 100644
index 000000000..659c8540e
--- /dev/null
+++ b/CCL/src/main/res/values-v11/styles.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-v21/styles.xml b/CCL/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..98b111abc
--- /dev/null
+++ b/CCL/src/main/res/values-v21/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-vi/strings.xml b/CCL/src/main/res/values-vi/strings.xml
new file mode 100755
index 000000000..69f270b1d
--- /dev/null
+++ b/CCL/src/main/res/values-vi/strings.xml
@@ -0,0 +1,97 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ Chơi trên...
+
+ Trực tuyến
+ Hủy
+ Bật
+ Tắt
+
+ Thông tin không có sẵn
+
+ OK
+ Lỗi
+ Đang truyền
+ đến %1$s
+
+ Đang tải…
+
+ Không có sẵn thông tin truyền thông
+
+ Đang cố gắng khôi
+ phục phiên trước đó...
+
+
+ Không thể chạy ứng dụng
+
+ Yêu
+ cầu chạy ứng dụng đã hết thời gian!
+
+
+ Ứng dụng mà bạn đang cố gắng chạy không có trên thiết bị Chromecast
+
+
+ Không thể bắt đầu phát lại truyền thông
+
+
+ Không thể ngừng phát lại truyền thông
+
+
+ Không thể tạm dừng phát lại truyền thông
+
+ Đã gặp
+ phải lỗi không xác định
+
+
+ Không thể kết nối với thiết bị
+
+ Không thể đặt âm
+ lượng
+
+ Hiện không có kết
+ nối với thiết bị truyền
+
+ Không có kết nối
+
+
+ Đã mất kết nối với thiết bị truyền. Ứng dụng đang cố gắng thiết lập lại kết nối, nếu có thể.
+ Vui lòng đợi vài giây và thử lại.
+
+ Không thể thực
+ hiện tác vụ
+
+ Không thể đồng bộ với
+ thiết bị truyền
+
+
+ Không thể tải phương tiện truyền thông trên thiết bị truyền
+
+ Không
+ thể tìm vị trí mới trên thiết bị truyền
+
+ Trình phát của
+ người nhận đã gặp phải lỗi máy chủ
+
+
+ Hết thời gian chờ ủy quyền
+
+ Không thể cập nhật kiểu
+ Phụ đề.
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-zh-rCN/strings.xml b/CCL/src/main/res/values-zh-rCN/strings.xml
new file mode 100755
index 000000000..e56b14b19
--- /dev/null
+++ b/CCL/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,87 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ 播放设备…
+ 直播
+ 取消
+ 开启
+ 关闭
+
+ 未提供信息
+
+ 确定
+ 错误
+ 正在投射到 %1$s
+
+ 正在加载…
+
+ 未提供媒体信息
+
+ 正在尝试恢复之前的会话…
+
+
+ 无法启动应用
+
+
+ 启动应用的请求已超时!
+
+
+ 您尝试启动的应用在 Chromecast 设备上不受支持
+
+
+ 无法开始播放媒体
+
+
+ 无法停止播放媒体
+
+
+ 无法暂停播放媒体
+
+
+ 遇到未知错误
+
+
+ 无法连接到设备
+
+ 无法设置音量
+
+ 投射设备上未显示任何连接
+
+ 无连接
+
+
+ 与投射设备的连接已断开。应用正在尝试重新建立连接(如果可能)。请等待几秒钟,然后重试。
+
+ 无法执行操作
+
+ 无法与投射设备同步
+
+
+ 无法在投射设备上加载媒体
+
+
+ 在投射设备上未能找到新位置
+
+ 接收端播放器发生错误
+
+
+ 授权已超时
+
+ 无法更新字幕样式。
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-zh-rHK/strings.xml b/CCL/src/main/res/values-zh-rHK/strings.xml
new file mode 100755
index 000000000..eb3a71833
--- /dev/null
+++ b/CCL/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,85 @@
+
+ 投放裝置配對資料庫
+ 1.10
+
+ 繼續遊戲…
+ 直播
+ 取消
+ 開啟
+ 關閉
+
+ 無法提供資料
+
+ 確定
+ 錯誤
+ 投放到 %1$s
+
+ 正在載入…
+
+ 沒有可用的媒體資料
+
+ 正在嘗試恢復之前的工作階段…
+
+
+ 無法啟動應用程式
+
+
+ 啟動應用程式的要求已過時!
+
+
+ 您正嘗試啟動的應用程式無法在 Chromecast 裝置上提供
+
+
+ 無法開始媒體播放
+
+
+ 無法停止媒體播放
+
+
+ 無法暫停媒體播放
+
+
+ 發生不明的錯誤
+
+
+ 無法連接到裝置
+
+ 無法設定音量
+
+ 投放裝置沒有連線
+
+ 沒有連線
+
+
+ 投放裝置的連線已中斷。應用程式正嘗試重新建立連線 (如適用)。請稍候數秒,然後再試一次。
+
+ 無法執行操作
+
+ 無法與投放裝置同步
+
+
+ 無法在投放裝置上載入媒體
+
+
+ 無法在投放裝置上找到新位置
+
+ 接收玩家發生嚴重錯誤
+
+
+ 授權已逾時
+
+ 無法更新「字幕」樣式。
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-zh-rTW/strings.xml b/CCL/src/main/res/values-zh-rTW/strings.xml
new file mode 100755
index 000000000..04781564c
--- /dev/null
+++ b/CCL/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,87 @@
+
+ Cast Companion
+ Library
+
+ 1.10 版
+
+ 繼續播放…
+ 直播
+ 取消
+ 開啟
+ 關閉
+
+ 無法提供資訊
+
+ 確定
+ 錯誤
+ 投放至「%1$s」
+
+ 載入中…
+
+ 沒有媒體資訊可提供
+
+ 正在嘗試復原上一個工作階段…
+
+
+ 無法啟動應用程式
+
+
+ 啟動應用程式的請求已逾時!
+
+
+ 您要啟動的應用程式無法在您的 Chromecast 裝置上使用
+
+
+ 無法開始播放媒體
+
+
+ 無法停止播放媒體
+
+
+ 無法暫停播放媒體
+
+
+ 發生不明錯誤
+
+
+ 無法與裝置連線
+
+ 無法設定音量
+
+ 目前沒有連到投放裝置的連線
+
+ 沒有連線
+
+
+ 與投放裝置的連線已中斷。應用程式正在嘗試重新建立連線 (如果可能的話),請於幾秒後再試一次。
+
+ 無法執行動作
+
+ 無法與投放裝置同步處理
+
+
+ 無法在投放裝置上載入媒體
+
+
+ 無法在投放裝置上找到新位置
+
+ 接收端播放器發生伺服器錯誤
+
+
+ 授權逾時
+
+ 無法更新「字幕」樣式。
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values-zh/strings.xml b/CCL/src/main/res/values-zh/strings.xml
new file mode 100755
index 000000000..e56b14b19
--- /dev/null
+++ b/CCL/src/main/res/values-zh/strings.xml
@@ -0,0 +1,87 @@
+
+ Cast Companion
+ Library
+
+ 1.10
+
+ 播放设备…
+ 直播
+ 取消
+ 开启
+ 关闭
+
+ 未提供信息
+
+ 确定
+ 错误
+ 正在投射到 %1$s
+
+ 正在加载…
+
+ 未提供媒体信息
+
+ 正在尝试恢复之前的会话…
+
+
+ 无法启动应用
+
+
+ 启动应用的请求已超时!
+
+
+ 您尝试启动的应用在 Chromecast 设备上不受支持
+
+
+ 无法开始播放媒体
+
+
+ 无法停止播放媒体
+
+
+ 无法暂停播放媒体
+
+
+ 遇到未知错误
+
+
+ 无法连接到设备
+
+ 无法设置音量
+
+ 投射设备上未显示任何连接
+
+ 无连接
+
+
+ 与投射设备的连接已断开。应用正在尝试重新建立连接(如果可能)。请等待几秒钟,然后重试。
+
+ 无法执行操作
+
+ 无法与投射设备同步
+
+
+ 无法在投射设备上加载媒体
+
+
+ 在投射设备上未能找到新位置
+
+ 接收端播放器发生错误
+
+
+ 授权已超时
+
+ 无法更新字幕样式。
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values/arrays.xml b/CCL/src/main/res/values/arrays.xml
new file mode 100644
index 000000000..f63d145ef
--- /dev/null
+++ b/CCL/src/main/res/values/arrays.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+ - Very Small
+ - Small
+ - Normal
+ - Large
+ - Very Large
+
+
+
+ - San-serif
+ - Serif
+ - Monospace
+
+
+
+ - 25%
+ - 50%
+ - 75%
+ - 100%
+
+
+
+ - None
+ - Outline
+ - Drop Shadow
+
+
+
+ - White
+ - Black
+ - Red
+ - Yellow
+ - Green
+ - Cyan
+ - Blue
+ - Magenta
+
+
+
+
+ - 0.5
+ - 0.75
+ - 1.0
+ - 1.5
+ - 2.0
+
+
+
+ - FONT_FAMILY_SANS_SERIF
+ - FONT_FAMILY_SERIF
+ - FONT_FAMILY_MONOSPACED_SANS_SERIF
+
+
+
+ - 3F
+ - 80
+ - BF
+ - FF
+
+
+
+ - EDGE_TYPE_NONE
+ - EDGE_TYPE_OUTLINE
+ - EDGE_TYPE_DROP_SHADOW
+
+
+
+ - #FFFFFF
+ - #000000
+ - #FF0000
+ - #FFFF00
+ - #00FF00
+ - #00FFFF
+ - #0000FF
+ - #FF00FF
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values/captions.xml b/CCL/src/main/res/values/captions.xml
new file mode 100644
index 000000000..76236fcef
--- /dev/null
+++ b/CCL/src/main/res/values/captions.xml
@@ -0,0 +1,58 @@
+
+
+
+ Caption
+
+ Caption Availability
+ Text Size
+ Font Family
+ Text Color
+ Text Opacity
+ Edge Type
+ Edge Color
+ Background Color
+ Background Opacity
+ Enabled
+ Disabled
+
+
+ ccl_caption_enabled
+ ccl_caption_font_family
+ ccl_caption_font_scale
+ ccl_caption_text_color
+ ccl_caption_text_opacity
+ ccl_caption_edge_type
+ ccl_caption_background_color
+ ccl_caption_font_background_opacity
+
+
+ 1.0
+ FONT_FAMILY_SERIF
+ #FFFFFF
+ FF
+ EDGE_TYPE_NONE
+ #000000
+ FF
+ Tracks
+
+ Subtitles
+ Audio
+ No Text Tracks Available
+ No Audio Tracks Available
+ No tracks available
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values/colors.xml b/CCL/src/main/res/values/colors.xml
new file mode 100644
index 000000000..e96b75bfb
--- /dev/null
+++ b/CCL/src/main/res/values/colors.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ #000000
+ #AA000000
+ #33b5e5
+
+
+ #E5FFFFFF
+ #E5FFFFFF
+ #E5AAAAAA
+ #000000
+ #FFFFFF
+
+ #4285f4
+ #555753
+ #03A9F4
+ #FFFFFF
+ #eeff41
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values/dimens.xml b/CCL/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..f5405d2b6
--- /dev/null
+++ b/CCL/src/main/res/values/dimens.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ 15sp
+ 13sp
+ 64dp
+ 64dp
+
+
+ 18sp
+ 18sp
+ 75dp
+
+ 300dp
+ 8dp
+
diff --git a/CCL/src/main/res/values/mini_controller.xml b/CCL/src/main/res/values/mini_controller.xml
new file mode 100644
index 000000000..0261a673a
--- /dev/null
+++ b/CCL/src/main/res/values/mini_controller.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/values/strings.xml b/CCL/src/main/res/values/strings.xml
new file mode 100644
index 000000000..854fc7738
--- /dev/null
+++ b/CCL/src/main/res/values/strings.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ 2.6
+ Play on…
+ Live
+ Cancel
+ On
+ Off
+ Information Not Available
+ Pause
+ Play
+ Disconnect
+ None
+
+
+ OK
+
+
+ Error
+
+
+ Casting to %1$s
+ Loading…
+
+
+ No media information available
+
+
+ Failed to launch application
+ The request to launch the application has timed out!
+ The application you are trying to launch is not available on your Cast device
+
+
+ Failed to start the playback of media
+ Failed to stop the playback of media
+ Failed to pause the playback of media
+
+
+ Could not connect to the device
+ Failed to set the volume
+ No connection to the cast device is present
+ No connection
+ Connection to the cast device has been lost. Application is trying to re-establish the connection, if possible. Please wait for a few seconds and try again.
+ Failed to perform the action
+ Failed to sync up with the cast device
+ Failed to seek to the new position on the cast device
+ Receiver player has encountered a severe error
+ Authorization timed out
+ Failed to update the captions style.
+ Up Next
+
+
diff --git a/CCL/src/main/res/values/styles.xml b/CCL/src/main/res/values/styles.xml
new file mode 100644
index 000000000..8a4faca0e
--- /dev/null
+++ b/CCL/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CCL/src/main/res/xml/caption_preference.xml b/CCL/src/main/res/xml/caption_preference.xml
new file mode 100644
index 000000000..580d5bcfc
--- /dev/null
+++ b/CCL/src/main/res/xml/caption_preference.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 8facad97b..71543664b 100644
--- a/README.md
+++ b/README.md
@@ -6,23 +6,37 @@ Google Santa Tracker for Android
[Google Santa Tracker app for Android][play-store] is an educational and entertaining tradition that brings joy to millions of children (and children at heart) across the world over the December holiday period. The app is a companion to the [Google Santa Tracker][santa-web] website ([repository here](https://github.com/google/santa-tracker-web)), showcasing unique platform capabilities like Android Wear watchfaces, device notifications and more.
![Analytics](https://ga-beacon.appspot.com/UA-12846745-20/santa-tracker-android/readme?pixel)
-![Village Screenshot](res/village.png)
+![Village Screenshot](res/village.png) ![Snowdown Screenshot](res/snowdown.png)
## Features
-* A beautiful parallax-scrolling village
-* 3 exciting games
-* 2 Android Wear watchfaces
-* Hidden Easter Eggs!
+* A beautiful materially designed village
+* 6 exciting games
+* 2 interactive Android Wear watchfaces (with sound!)
+* Videos, animations and more.
## Building the app
-It's simple. Plug your phone in (or fire up an emulator) and run:
+First up, Santa Tracker is powered by [Firebase][firebase], so you'll need to enable it
+on your Google account over at the [Firebase console][fire-console]. Once you're in the
+console, follow these steps:
- ./gradlew installDebug
+ * Create a new project
+ * Add Firebase to your Android app
+ * Package name: `com.google.android.apps.santatracker.debug`
+ * Debug signing certificate can be blank, or follow the instructions in the
+ tooltip to find yours.
+ * Save the google-services.json file to the santa-tracker/ directory
+
+Now you should be able to plug your phone in (or fire up an emulator) and run:
+
+ ./gradlew santa-tracker:installDebug
Alternatively, import the source code into Android Studio (File, Import Project).
+Note: You'll need Android SDK version 23 and build tools 23.0.1 to compile the project. If
+you're unsure about this, use Android Studio and tick the appropriate boxes in the SDK Manager.
+
## License
All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav
and *.ogg) are licensed under the CC-BY-NC license. All other files are
@@ -46,3 +60,5 @@ licensed under the Apache 2 license. See the LICENSE file for details.
[play-store]: https://play.google.com/store/apps/details?id=com.google.android.apps.santatracker
[santa-web]: http://g.co/santatracker
+[firebase]: https://firebase.google.com/
+[fire-console]: https://firebase.google.com/console/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..5e5d1ab10
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,66 @@
+buildscript {
+ ext.androidHome = project.hasProperty('androidHome') ? androidHome : '../../../..'
+
+ repositories {
+ // Required for offline build.
+ // Links to the internal repositories for standard tools.
+ maven { url "$androidHome/prebuilts/gradle-plugin" }
+ maven { url "$androidHome/prebuilts/tools/common/m2/repository" }
+
+ // When adding new dependencies, first enable jcenter(), then use the
+ // scripts/copyOfflineDep.sh to copy the dependency into the offline-lib-repository folder
+ maven { url "third_party/offline-lib-repository" }
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.2'
+ classpath 'com.google.gms:google-services:3.0.0'
+ }
+}
+
+subprojects {
+ repositories {
+ maven {
+ url "../third_party/offline-lib-repository"
+ }
+ }
+}
+
+ext {
+ tools = '23.0.1'
+ support = '23.4.0'
+ supportV4 = "com.android.support:support-v4:$support"
+ supportAnnotations = "com.android.support:support-annotations:$support"
+ appCompat = "com.android.support:appcompat-v7:$support"
+ design = "com.android.support:design:$support"
+ mediaRouter = "com.android.support:mediarouter-v7:$support"
+ supportWearable = "com.google.android.support:wearable:1.4.0"
+
+ cardView = "com.android.support:cardview-v7:$support"
+ recyclerView = "com.android.support:recyclerview-v7:$support"
+ leanback = "com.android.support:leanback-v17:$support"
+ multidex = 'com.android.support:multidex:1.0.0'
+
+ play = '9.2.1'
+
+ playServicesAnalytics = "com.google.android.gms:play-services-analytics:$play"
+ playServicesAppindexing = "com.google.android.gms:play-services-appindexing:$play"
+ playServicesBase = "com.google.android.gms:play-services-base:$play"
+ playServicesBasement = "com.google.android.gms:play-services-basement:$play"
+ playServicesCast = "com.google.android.gms:play-services-cast:$play"
+ playServicesGames = "com.google.android.gms:play-services-games:$play"
+ playServicesMaps = "com.google.android.gms:play-services-maps:$play"
+ playServicesNearby = "com.google.android.gms:play-services-nearby:$play"
+ playServicesPlus = "com.google.android.gms:play-services-plus:$play"
+ playServicesWearable = "com.google.android.gms:play-services-wearable:$play"
+
+ firebaseCore = "com.google.firebase:firebase-core:$play"
+ firebaseAnalytics = "com.google.firebase:firebase-analytics:$play"
+ firebaseAppinvite = "com.google.firebase:firebase-invites:$play"
+ firebaseConfig = "com.google.firebase:firebase-config:$play"
+
+ androidMapsUtils = 'com.google.maps.android:android-maps-utils:0.4'
+
+ seismic = "com.squareup:seismic:1.0.2"
+ glide = "com.github.bumptech.glide:glide:3.6.1"
+}
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/common/build.gradle b/common/build.gradle
new file mode 100644
index 000000000..757fa6cf2
--- /dev/null
+++ b/common/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 23
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+
+ compile rootProject.ext.supportV4
+
+ compile rootProject.ext.playServicesAnalytics
+ compile rootProject.ext.playServicesBase
+ compile rootProject.ext.playServicesBasement
+ compile rootProject.ext.playServicesGames
+
+ compile rootProject.ext.firebaseCore
+ compile rootProject.ext.firebaseAnalytics
+ compile rootProject.ext.firebaseAppinvite
+}
\ No newline at end of file
diff --git a/common/proguard-rules.pro b/common/proguard-rules.pro
new file mode 100644
index 000000000..e2c580fb8
--- /dev/null
+++ b/common/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/lwray/Desktop/Stuff/adt-bundle-mac-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..aaff8795b
--- /dev/null
+++ b/common/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/common/src/main/java/com/google/android/apps/santatracker/AudioPlayer.java b/common/src/main/java/com/google/android/apps/santatracker/AudioPlayer.java
new file mode 100644
index 000000000..8b8609a00
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/AudioPlayer.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.util.SparseArray;
+
+public class AudioPlayer {
+
+ private Context mContext;
+ private SparseArray mStreams;
+ private boolean mMuted = false;
+
+ private static float VOLUME_MULTIPLIER = 0.25f;
+
+ public AudioPlayer(Context context) {
+ mContext = context;
+ mStreams = new SparseArray<>();
+ this.mMuted = false;
+ }
+
+ public void playTrack(final int resId, final boolean loop) {
+ MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resId);
+ // Not all devices support audio (i.e. watches)
+ if (mediaPlayer != null) {
+ mStreams.put(resId, mediaPlayer);
+ mediaPlayer.setLooping(loop);
+ mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ startMedia(mp);
+ }
+ });
+ mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ if (!mp.isLooping()) {
+ mp.release();
+ mStreams.remove(resId);
+ }
+ }
+ });
+ }
+ }
+
+ public void playTrackIfNotAlreadyPlaying(final int resId, final boolean loop) {
+ if (mStreams.get(resId) == null) {
+ playTrack(resId, loop);
+ }
+ }
+
+ public void playTrackExclusive(final int resId, final boolean loop) {
+ boolean restart = false;
+ MediaPlayer mp = mStreams.get(resId);
+ try {
+ if (mp == null || !mp.isPlaying()) {
+ restart = true;
+ }
+ } catch (IllegalStateException e) {
+ // Media player was not initialised or was released
+ restart = true;
+ }
+
+ if (restart) {
+ stopAll();
+ playTrack(resId, loop);
+ }
+ }
+
+ private void startMedia(MediaPlayer mp) {
+ if (mMuted) {
+ mp.setVolume(0f, 0f);
+ } else {
+ mp.setVolume(VOLUME_MULTIPLIER, VOLUME_MULTIPLIER);
+ }
+ mp.start();
+ }
+
+ public void stop(int resId) {
+ MediaPlayer mp = mStreams.get(resId);
+ if (mp != null) {
+ mp.stop();
+ mp.release();
+ mStreams.remove(resId);
+ }
+ }
+
+ public void muteAll() {
+ mMuted = true;
+ for (int i = 0; i < mStreams.size(); i++) {
+ mStreams.valueAt(i).setVolume(0f, 0f);
+ }
+ }
+
+ public void unMuteAll() {
+ mMuted = false;
+ for (int i = 0; i < mStreams.size(); i++) {
+ mStreams.valueAt(i).setVolume(VOLUME_MULTIPLIER, VOLUME_MULTIPLIER);
+ }
+ }
+
+ public void pauseAll() {
+ for (int i = 0; i < mStreams.size(); i++) {
+ mStreams.valueAt(i).pause();
+ }
+ }
+
+ public void resumeAll() {
+ for (int i = 0; i < mStreams.size(); i++) {
+ startMedia(mStreams.valueAt(i));
+ }
+ }
+
+ public void stopAll() {
+ // Stop all audio
+ for (int i = 0; i < mStreams.size(); i++) {
+ MediaPlayer mp = mStreams.valueAt(i);
+ mp.release();
+ mStreams.removeAt(i);
+ }
+ }
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/common/NotificationConstants.java b/common/src/main/java/com/google/android/apps/santatracker/common/NotificationConstants.java
new file mode 100644
index 000000000..e2f02d079
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/common/NotificationConstants.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.common;
+
+/**
+ * Constants that are used in both the Application and the Wearable modules.
+ */
+public final class NotificationConstants {
+
+ public static final String KEY_LOCATION = "location";
+
+ private NotificationConstants() {};
+
+ // Only one ID because we only show one notification at a time.
+ public static final int NOTIFICATION_ID = 9876435;
+
+ public static final int NOTIFICATION_TAKEOFF = 1;
+ public static final int NOTIFICATION_NEARBY = 2; //Ex: "Santa is 3 hours away from you."
+ public static final int NOTIFICATION_LOCATION = 3; //Ex: "Santa is currently over Australia!"
+ public static final int NOTIFICATION_FACT = 4; // Santa status update or factoid
+
+ public static final String TAKEOFF_PATH = "/takeoff";
+ public static final String KEY_NOTIFICATION_ID = "notification-id";
+ public static final String KEY_NOTIFICATION_TYPE = "notification-type";
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_CONTENT = "content";
+ public static final String KEY_LOCATION_PHOTO = "location-photo";
+ public static final String KEY_LOCATION_MAP = "location-map";
+ public static final String KEY_LOCATION_FACT = "location-fact";
+ public static final String KEY_TIMESTAMP = "timestap";
+ public static final String KEY_FINAL_ARRIVAL = "finalArrival";
+ public static final String KEY_FACT = "fact";
+ public static final String KEY_STATUS = "status";
+ public static final String KEY_IMAGEURL = "imageurl";
+
+ public static final String ACTION_DISMISS
+ = "com.google.android.apps.santatracker.DISMISS";
+
+ public static final String ACTION_SEND
+ = "com.google.android.apps.santatracker.SEND";
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/games/PlayGamesFragment.java b/common/src/main/java/com/google/android/apps/santatracker/games/PlayGamesFragment.java
new file mode 100644
index 000000000..f4a6081ed
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/games/PlayGamesFragment.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 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.games;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.common.BuildConfig;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.games.Games;
+
+/**
+ * Non-visible fragment to encapsulate Google Play Game Services logic.
+ */
+public class PlayGamesFragment extends Fragment implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener{
+
+ private static final String TAG = "PlayGamesFragment";
+ private static final String FRAGMENT_TAG = "PlayGamesFragment_Tag";
+
+ /** Key to store mIsResolving in SharedPreferences. **/
+ private static final String KEY_IS_RESOLVING = "is_resolving";
+
+ /** Key to store mShouldResolve in SharedPreferences. **/
+ private static final String KEY_SHOULD_RESOLVE = "should_resolve";
+
+ /** Request code used for resolving Games sign-in failures. **/
+ private static final int RC_GAMES = 9001;
+
+ /** Should debug-level log messages be printed? **/
+ private boolean mDebugLogEnabled = false;
+
+ /** GoogleApiClient used for interacting with Play Game Services. **/
+ private GoogleApiClient mGamesApiClient;
+
+ /** Is a resolution already in progress? **/
+ private boolean mIsResolving = false;
+
+ /** Should connection failures be automatically resolved? **/
+ private boolean mShouldResolve = false;
+
+ /** Listener for sign-in events. **/
+ private SignInListener mListener;
+
+ /**
+ * Get or create an instance of the Fragment attached to an Activity.
+ * @param activity FragmentActivity to host the Fragment.
+ * @param listener SignInListener to respond to changes in sign-in state.
+ * @return instance of PlayGamesFragment.
+ */
+ public static PlayGamesFragment getInstance(FragmentActivity activity,
+ SignInListener listener) {
+
+ FragmentManager fm = activity.getSupportFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+
+ PlayGamesFragment result = null;
+
+ Fragment fragment = fm.findFragmentByTag(FRAGMENT_TAG);
+ if (fragment == null) {
+ result = new PlayGamesFragment();
+ ft.add(result, FRAGMENT_TAG).disallowAddToBackStack().commit();
+ } else {
+ result = (PlayGamesFragment) fragment;
+ }
+
+ result.setListener(listener);
+ return result;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Restore state of in-progress sign-in in the case of rotation or other
+ // Activity recreation.
+ if (savedInstanceState != null) {
+ mIsResolving = savedInstanceState.getBoolean(KEY_IS_RESOLVING, false);
+ mShouldResolve = savedInstanceState.getBoolean(KEY_SHOULD_RESOLVE, false);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mGamesApiClient.connect();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mGamesApiClient.disconnect();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Only log debug messages when enabled
+ mDebugLogEnabled = BuildConfig.DEBUG;
+
+ // Api client for interacting with Google Play Games
+ mGamesApiClient = new GoogleApiClient.Builder(getActivity())
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(Games.API, Games.GamesOptions.builder().build())
+ .addScope(Games.SCOPE_GAMES)
+ .build();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == RC_GAMES) {
+ debugLog("onActivityResult:RC_GAMES:" + resultCode + ":" + data);
+
+ // If the error resolution was not successful we should not resolve further.
+ if (resultCode != Activity.RESULT_OK) {
+ mShouldResolve = false;
+ }
+
+ mIsResolving = false;
+ mGamesApiClient.connect();
+ }
+ }
+
+ @Override
+ public void onConnected(Bundle bundle) {
+ debugLog("onConnected:" + bundle);
+ mShouldResolve = false;
+ mListener.onSignInSucceeded();
+ }
+
+ @Override
+ public void onConnectionSuspended(int i) {
+ debugLog("onConnectionSuspended:" + i);
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult connectionResult) {
+ debugLog("onConnectionFailed:" + connectionResult);
+ if (!mIsResolving && mShouldResolve) {
+ if (connectionResult.hasResolution()) {
+ try {
+ connectionResult.startResolutionForResult(getActivity(), RC_GAMES);
+ mIsResolving = true;
+ } catch (IntentSender.SendIntentException e) {
+ debugLog("onConnectionFailed:SendIntentException:" + e.getMessage());
+ mIsResolving = false;
+ mGamesApiClient.connect();
+ }
+ } else {
+ // Could not resolve the connection result, show the user an
+ // error dialog.
+ showErrorDialog(connectionResult);
+ }
+ } else {
+ // Show the signed-out UI
+ mListener.onSignInFailed();
+ }
+ }
+
+ /**
+ * Show error dialog for Google Play Services errors that cannot be resolved.
+ * @param connectionResult the connection result from onConnectionFailed.
+ */
+ private void showErrorDialog(ConnectionResult connectionResult) {
+ GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
+ int resultCode = apiAvailability.isGooglePlayServicesAvailable(getActivity());
+
+ if (resultCode != ConnectionResult.SUCCESS) {
+ if (apiAvailability.isUserResolvableError(resultCode)) {
+ apiAvailability.getErrorDialog(getActivity(), resultCode, RC_GAMES,
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mShouldResolve = false;
+ mListener.onSignInFailed();
+ }
+ }).show();
+ } else {
+ String errorString = apiAvailability.getErrorString(resultCode);
+ debugLog("Google Play Services Error:" + connectionResult + ":" + errorString);;
+
+ mShouldResolve = false;
+ mListener.onSignInFailed();
+ }
+ }
+ }
+
+ public boolean isSignedIn() {
+ return (mGamesApiClient != null && mGamesApiClient.isConnected());
+ }
+
+ public void beginUserInitiatedSignIn() {
+ mShouldResolve = true;
+ mGamesApiClient.connect();
+ }
+
+ public GoogleApiClient getGamesApiClient() {
+ return mGamesApiClient;
+ }
+
+ private void debugLog(String message) {
+ if (!mDebugLogEnabled) {
+ return;
+ }
+
+ Log.d(TAG, message);
+ }
+
+ private void setListener(SignInListener listener) {
+ mListener = listener;
+ }
+
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/games/SignInListener.java b/common/src/main/java/com/google/android/apps/santatracker/games/SignInListener.java
new file mode 100644
index 000000000..66093ede0
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/games/SignInListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.games;
+
+/**
+ * Interface for Activities that observe sign-in state, normally used with {@link GameActivity}.
+ */
+public interface SignInListener {
+
+ /**
+ * Called when sign-in fails. As a result, a "Sign-In" button can be shown to the user.
+ * Not all calls to this method indicate an error, sign-in is expected to fail when the
+ * user has never signed in before.
+ */
+ void onSignInFailed();
+
+ /**
+ * Called when sign-in succeeds and the application can begin to take action on behalf of
+ * the user.
+ */
+ void onSignInSucceeded();
+
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/invites/AppInvitesFragment.java b/common/src/main/java/com/google/android/apps/santatracker/invites/AppInvitesFragment.java
new file mode 100644
index 000000000..7703edda3
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/invites/AppInvitesFragment.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2015 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.invites;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.common.R;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.gms.appinvite.AppInvite;
+import com.google.android.gms.appinvite.AppInviteInvitation;
+import com.google.android.gms.appinvite.AppInviteInvitationResult;
+import com.google.android.gms.appinvite.AppInviteReferral;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class AppInvitesFragment extends Fragment implements
+ GoogleApiClient.OnConnectionFailedListener {
+
+ private static final String TAG = "AppInvitesFragment";
+ private static final String FRAGMENT_TAG = "AppInvitesFragment";
+ private static final int AUTOMANAGE_ID = 107;
+ private static final int RC_INVITE = 9007;
+
+ private static final Uri BASE_URI = Uri.parse("http://google.com/santatracker/android/");
+
+ private GoogleApiClient mGoogleApiClient;
+ private FirebaseAnalytics mMeasurement;
+
+ public interface GetInvitationCallback {
+ void onInvitation(String invitationId, String deepLink);
+ }
+
+ public static AppInvitesFragment getInstance(FragmentActivity activity) {
+
+ FragmentManager fm = activity.getSupportFragmentManager();
+ FragmentTransaction ft = fm.beginTransaction();
+
+ AppInvitesFragment result = null;
+
+ Fragment fragment = fm.findFragmentByTag(FRAGMENT_TAG);
+ if (fragment == null) {
+ result = new AppInvitesFragment();
+ ft.add(result, FRAGMENT_TAG).disallowAddToBackStack().commit();
+ } else {
+ result = (AppInvitesFragment) fragment;
+ }
+
+ return result;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Initialize app measurement.
+ mMeasurement = FirebaseAnalytics.getInstance(getActivity());
+
+ // Api client for AppInvites.
+ mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
+ .addOnConnectionFailedListener(this)
+ .enableAutoManage(getActivity(), AUTOMANAGE_ID, this)
+ .addApi(AppInvite.API)
+ .build();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == RC_INVITE) {
+ String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data);
+ Log.d(TAG, "onActivityResult:" + ids);
+ }
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult connectionResult) {
+ Log.w(TAG, "onConnectionFailed:" + connectionResult);
+ }
+
+ /**
+ * Send an invite with a deep link into a game.
+ * @param gameName a human-readable name for the game, to be displayed in the invitation UI.
+ * @param gameId an identifier for the game to be appended to
+ * http://google.com/santatracker/android/. The game should be a registered
+ * handler for this URL in the Android Manifest.
+ * @param score the inviting user's game score, which will be pre-populated in the
+ * invitation message.
+ */
+ public void sendGameInvite(String gameName, String gameId, int score) {
+ Uri uri = BASE_URI.buildUpon()
+ .appendPath(gameId)
+ .appendQueryParameter("score", Integer.toString(score))
+ .build();
+
+ sendInvite(getString(R.string.invite_message_game_fmt, score, gameName), uri);
+ MeasurementManager.recordInvitationSent(mMeasurement, "game", uri.toString());
+
+ }
+
+ public void sendGenericInvite() {
+ Uri uri = BASE_URI;
+ sendInvite(getString(R.string.invite_message_generic), uri);
+ MeasurementManager.recordInvitationSent(mMeasurement, "generic", uri.toString());
+ }
+
+ private void sendInvite(String message, Uri uri) {
+ // If the message is too long, just cut it short and add ellipses. This is something that
+ // only occurs in some translations and we do not have a better mitigation method. The
+ // alternative is an ugly IllegalArgumentException from the builder.
+ int maxLength = AppInviteInvitation.IntentBuilder.MAX_MESSAGE_LENGTH;
+ if (message.length() > maxLength) {
+ String suffix = "...";
+ String prefix = message.substring(0, maxLength - suffix.length());
+
+ message = prefix + suffix;
+ }
+
+ Intent inviteIntent = new AppInviteInvitation.IntentBuilder(getString(R.string.invite_title))
+ .setMessage(message)
+ .setDeepLink(uri)
+ .build();
+
+ startActivityForResult(inviteIntent, RC_INVITE);
+ }
+
+ public void getInvite(final GetInvitationCallback callback, boolean launchDeepLink) {
+ AppInvite.AppInviteApi.getInvitation(mGoogleApiClient, getActivity(), launchDeepLink)
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(AppInviteInvitationResult appInviteInvitationResult) {
+ Log.d(TAG, "getInvite:" + appInviteInvitationResult.getStatus());
+
+ if (callback != null && appInviteInvitationResult.getStatus().isSuccess()) {
+ // Report the callback.
+ Intent intent = appInviteInvitationResult.getInvitationIntent();
+ String invitiationId = AppInviteReferral.getInvitationId(intent);
+ String deepLink = AppInviteReferral.getDeepLink(intent);
+ callback.onInvitation(invitiationId, deepLink);
+
+ // Record invitation receipt event.
+ MeasurementManager.recordInvitationReceived(mMeasurement, deepLink);
+ }
+
+ }
+ });
+ }
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/util/AnalyticsManager.java b/common/src/main/java/com/google/android/apps/santatracker/util/AnalyticsManager.java
new file mode 100644
index 000000000..d2198dbbc
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/util/AnalyticsManager.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 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.util;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.android.gms.analytics.HitBuilders;
+import com.google.android.gms.analytics.Tracker;
+import com.google.android.apps.santatracker.common.R;
+
+/**
+ * Handles communication with Google Analytics.
+ * Based on implementation in iosched: com.google.samples.apps.iosched.util.AnalyticsManager
+ */
+public class AnalyticsManager {
+
+ private static Context sAppContext = null;
+ private static Tracker mTracker;
+
+ private final static String TAG = "AnalyticsManager";
+
+ public static synchronized void setTracker(Tracker tracker) {
+ mTracker = tracker;
+ }
+
+ private static boolean canSend() {
+ return mTracker != null;
+ }
+
+ /**
+ * Sends a screen view with the string resource loaded as its label.
+ */
+ public static void sendScreenView(int resourceId) {
+ sendScreenView(getString(resourceId));
+ }
+
+ private static String getString(int id){
+ if(sAppContext != null) {
+ return sAppContext.getString(id);
+ }
+ return null;
+ }
+ /**
+ * Sends a screen vie for a screen label.
+ */
+ public static void sendScreenView(String screenName) {
+ if (canSend()) {
+ mTracker.setScreenName(screenName);
+ mTracker.send(new HitBuilders.AppViewBuilder().build());
+ Log.d(TAG, "Screen View recorded: " + screenName);
+ } else {
+ Log.d(TAG, "Screen View NOT recorded (analytics disabled or not ready).");
+ }
+ }
+
+ /**
+ * Sends an event to the tracker with string resources loaded as parameters.
+ */
+ public static void sendEvent(int category, int action, int label, long value) {
+ sendEvent(getString(category), getString(action),
+ getString(label), value);
+ }
+
+ /**
+ * Sends an event to the tracker with string resources loaded as parameters.
+ */
+ public static void sendEvent(int category, int action) {
+ sendEvent(getString(category), getString(action));
+ }
+
+ /**
+ * Sends an event to the tracker with string resources loaded as parameters.
+ */
+ public static void sendEvent(int category, int action, String label) {
+ sendEvent(getString(category), getString(action), label);
+ }
+
+ public static void sendEvent(String category, String action) {
+ if (canSend()) {
+ mTracker.send(new HitBuilders.EventBuilder(category,action).build());
+
+ Log.d(TAG, "Event recorded:");
+ Log.d(TAG, "\tCategory: " + category);
+ Log.d(TAG, "\tAction: " + action);
+ } else {
+ Log.d(TAG, "Analytics event ignored (analytics disabled or not ready).");
+ }
+ }
+
+ public static void sendEvent(String category, String action, String label, long value) {
+ if (canSend()) {
+ mTracker.send(new HitBuilders.EventBuilder()
+ .setCategory(category)
+ .setAction(action)
+ .setLabel(label)
+ .setValue(value)
+ .build());
+
+ Log.d(TAG, "Event recorded:");
+ Log.d(TAG, "\tCategory: " + category);
+ Log.d(TAG, "\tAction: " + action);
+ Log.d(TAG, "\tLabel: " + label);
+ Log.d(TAG, "\tValue: " + value);
+ } else {
+ Log.d(TAG, "Analytics event ignored (analytics disabled or not ready).");
+ }
+ }
+
+ /**
+ * Sends an event to the tracker with string resources loaded as parameters.
+ */
+ public static void sendEvent(int category, int action, int label) {
+ sendEvent(getString(category), getString(action),
+ getString(label));
+ }
+
+ public static void sendEvent(String category, String action, String label) {
+ sendEvent(category, action, label, 0);
+ }
+
+ public Tracker getTracker() {
+ return mTracker;
+ }
+
+ public static synchronized void initializeAnalyticsTracker(Context context) {
+ sAppContext = context;
+ if (mTracker == null) {
+ GoogleAnalytics analytics = GoogleAnalytics.getInstance(context);
+ mTracker = analytics.newTracker(R.xml.config_analytics_tracker);
+ Log.d(TAG, "Analytics tracker initialised.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/google/android/apps/santatracker/util/ImmersiveModeHelper.java b/common/src/main/java/com/google/android/apps/santatracker/util/ImmersiveModeHelper.java
new file mode 100644
index 000000000..aa4ba29ca
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/util/ImmersiveModeHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+
+public class ImmersiveModeHelper {
+
+ private static final String TAG = ImmersiveModeHelper.class
+ .getSimpleName();
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void installSystemUiVisibilityChangeListener(final Window w) {
+ View view = w.getDecorView();
+ view.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
+ @Override
+ public void onSystemUiVisibilityChange(int visibility) {
+ Log.d(TAG, "setOnSystemUiVisibilityChangeListener: visibility=" + visibility);
+ setImmersiveSticky(w);
+ }
+ });
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void setImmersiveSticky(Window w) {
+ w.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static void setImmersiveStickyWithActionBar(Window w) {
+ w.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ }
+}
diff --git a/common/src/main/java/com/google/android/apps/santatracker/util/MeasurementManager.java b/common/src/main/java/com/google/android/apps/santatracker/util/MeasurementManager.java
new file mode 100644
index 000000000..2e37a3928
--- /dev/null
+++ b/common/src/main/java/com/google/android/apps/santatracker/util/MeasurementManager.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/** Handles communication with Firebase Analytics. */
+public class MeasurementManager {
+
+ private static final String TAG = "MeasurementManager";
+
+ private static final String GAME_TITLE = "game_title";
+ private static final String TYPE_SCREEN = "type_screen";
+
+ public static void recordCustomEvent(FirebaseAnalytics measurement,
+ @NonNull String name,
+ @NonNull String action,
+ @Nullable String label) {
+ Log.d(TAG, "recordCustomEvent:" + name + ":" + action + ":" + label);
+
+ Bundle params = new Bundle();
+ params.putString("action", action);
+ if (label != null) {
+ params.putString("label", label);
+ }
+ measurement.logEvent(name, params);
+ }
+
+ public static void recordCustomEvent(FirebaseAnalytics measurement,
+ @NonNull String name,
+ @NonNull String action) {
+ Log.d(TAG, "recordCustomEvent:" + name + ":" + action);
+
+ recordCustomEvent(measurement, name, action, null);
+ }
+
+ public static void recordScreenView(FirebaseAnalytics measurement,
+ @NonNull String id) {
+ Log.d(TAG, "recordScreenView:" + id);
+
+ Bundle params = new Bundle();
+ params.putString(FirebaseAnalytics.Param.CONTENT_TYPE, TYPE_SCREEN);
+ params.putString(FirebaseAnalytics.Param.ITEM_ID, id);
+ measurement.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, params);
+ }
+
+ public static void recordInvitationReceived(FirebaseAnalytics measurement,
+ @NonNull String deepLink) {
+ Log.d(TAG, "recordInvitationReceived:" + deepLink);
+
+ Bundle params = new Bundle();
+ params.putString("deepLink", deepLink);
+ measurement.logEvent(FirebaseAnalytics.Event.APP_OPEN, params);
+ }
+
+ public static void recordInvitationSent(FirebaseAnalytics measurement,
+ @NonNull String type,
+ @NonNull String deepLink) {
+ Log.d(TAG, "recordInvitationSent:" + type + ":" + deepLink);
+
+ Bundle params = new Bundle();
+ params.putString(FirebaseAnalytics.Param.CONTENT_TYPE, type);
+ params.putSerializable(FirebaseAnalytics.Param.ITEM_ID, deepLink);
+ measurement.logEvent(FirebaseAnalytics.Event.SHARE, params);
+ }
+
+ public static void recordLogin(FirebaseAnalytics measurement) {
+ Log.d(TAG, "recordLogin");
+ measurement.logEvent(FirebaseAnalytics.Event.LOGIN, null);
+ }
+
+ public static void recordAchievement(FirebaseAnalytics measurement,
+ @NonNull String achId,
+ @Nullable String gameTitle) {
+ Log.d(TAG, "recordAchievement:" + achId + ":" + gameTitle);
+
+ Bundle params = new Bundle();
+ params.putString(FirebaseAnalytics.Param.ACHIEVEMENT_ID, achId);
+ if (gameTitle != null) {
+ params.putString(GAME_TITLE, gameTitle);
+ }
+ measurement.logEvent(FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT, params);
+ }
+
+ public static void recordGameScore(FirebaseAnalytics measurement,
+ @NonNull Long score,
+ @Nullable Long level,
+ @Nullable String gameTitle) {
+ Log.d(TAG, "recordGameEnd:" + gameTitle + ":" + score + ":" + level);
+
+ Bundle params = new Bundle();
+ params.putLong(FirebaseAnalytics.Param.SCORE, score);
+ if (level != null) {
+ params.putLong(FirebaseAnalytics.Param.LEVEL, level);
+ }
+ if (gameTitle != null) {
+ params.putString(GAME_TITLE, gameTitle);
+ }
+ measurement.logEvent(FirebaseAnalytics.Event.POST_SCORE, params);
+ }
+
+}
diff --git a/common/src/main/res/drawable-hdpi/games_share.png b/common/src/main/res/drawable-hdpi/games_share.png
new file mode 100644
index 000000000..1c693a867
Binary files /dev/null and b/common/src/main/res/drawable-hdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-hdpi/games_share_pressed.png b/common/src/main/res/drawable-hdpi/games_share_pressed.png
new file mode 100644
index 000000000..d96b26981
Binary files /dev/null and b/common/src/main/res/drawable-hdpi/games_share_pressed.png differ
diff --git a/common/src/main/res/drawable-hdpi/signin.9.png b/common/src/main/res/drawable-hdpi/signin.9.png
new file mode 100644
index 000000000..3ed145a26
Binary files /dev/null and b/common/src/main/res/drawable-hdpi/signin.9.png differ
diff --git a/common/src/main/res/drawable-hdpi/signin_pressed.9.png b/common/src/main/res/drawable-hdpi/signin_pressed.9.png
new file mode 100644
index 000000000..c98185ccf
Binary files /dev/null and b/common/src/main/res/drawable-hdpi/signin_pressed.9.png differ
diff --git a/common/src/main/res/drawable-mdpi/games_share.png b/common/src/main/res/drawable-mdpi/games_share.png
new file mode 100644
index 000000000..f6b58b926
Binary files /dev/null and b/common/src/main/res/drawable-mdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-mdpi/games_share_pressed.png b/common/src/main/res/drawable-mdpi/games_share_pressed.png
new file mode 100644
index 000000000..218c73df6
Binary files /dev/null and b/common/src/main/res/drawable-mdpi/games_share_pressed.png differ
diff --git a/common/src/main/res/drawable-mdpi/signin.9.png b/common/src/main/res/drawable-mdpi/signin.9.png
new file mode 100644
index 000000000..a27c66fc7
Binary files /dev/null and b/common/src/main/res/drawable-mdpi/signin.9.png differ
diff --git a/common/src/main/res/drawable-mdpi/signin_pressed.9.png b/common/src/main/res/drawable-mdpi/signin_pressed.9.png
new file mode 100644
index 000000000..fe7cc979d
Binary files /dev/null and b/common/src/main/res/drawable-mdpi/signin_pressed.9.png differ
diff --git a/common/src/main/res/drawable-sw600dp-hdpi/games_share.png b/common/src/main/res/drawable-sw600dp-hdpi/games_share.png
new file mode 100644
index 000000000..bf333cc96
Binary files /dev/null and b/common/src/main/res/drawable-sw600dp-hdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-sw600dp-mdpi/games_share.png b/common/src/main/res/drawable-sw600dp-mdpi/games_share.png
new file mode 100644
index 000000000..76942ac3e
Binary files /dev/null and b/common/src/main/res/drawable-sw600dp-mdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-sw600dp-tvdpi/games_share.png b/common/src/main/res/drawable-sw600dp-tvdpi/games_share.png
new file mode 100644
index 000000000..76942ac3e
Binary files /dev/null and b/common/src/main/res/drawable-sw600dp-tvdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-xhdpi/games_share.png b/common/src/main/res/drawable-xhdpi/games_share.png
new file mode 100644
index 000000000..76942ac3e
Binary files /dev/null and b/common/src/main/res/drawable-xhdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-xhdpi/games_share_pressed.png b/common/src/main/res/drawable-xhdpi/games_share_pressed.png
new file mode 100644
index 000000000..bd2353d7c
Binary files /dev/null and b/common/src/main/res/drawable-xhdpi/games_share_pressed.png differ
diff --git a/common/src/main/res/drawable-xhdpi/signin.9.png b/common/src/main/res/drawable-xhdpi/signin.9.png
new file mode 100644
index 000000000..09c9991ed
Binary files /dev/null and b/common/src/main/res/drawable-xhdpi/signin.9.png differ
diff --git a/common/src/main/res/drawable-xhdpi/signin_pressed.9.png b/common/src/main/res/drawable-xhdpi/signin_pressed.9.png
new file mode 100644
index 000000000..2e61874ce
Binary files /dev/null and b/common/src/main/res/drawable-xhdpi/signin_pressed.9.png differ
diff --git a/common/src/main/res/drawable-xxhdpi/games_share.png b/common/src/main/res/drawable-xxhdpi/games_share.png
new file mode 100644
index 000000000..bf333cc96
Binary files /dev/null and b/common/src/main/res/drawable-xxhdpi/games_share.png differ
diff --git a/common/src/main/res/drawable-xxhdpi/games_share_pressed.png b/common/src/main/res/drawable-xxhdpi/games_share_pressed.png
new file mode 100644
index 000000000..363a2eb99
Binary files /dev/null and b/common/src/main/res/drawable-xxhdpi/games_share_pressed.png differ
diff --git a/common/src/main/res/drawable-xxhdpi/signin.9.png b/common/src/main/res/drawable-xxhdpi/signin.9.png
new file mode 100644
index 000000000..9119ae642
Binary files /dev/null and b/common/src/main/res/drawable-xxhdpi/signin.9.png differ
diff --git a/common/src/main/res/drawable-xxhdpi/signin_pressed.9.png b/common/src/main/res/drawable-xxhdpi/signin_pressed.9.png
new file mode 100644
index 000000000..d0dcc4446
Binary files /dev/null and b/common/src/main/res/drawable-xxhdpi/signin_pressed.9.png differ
diff --git a/common/src/main/res/drawable-xxxhdpi/signin.9.png b/common/src/main/res/drawable-xxxhdpi/signin.9.png
new file mode 100644
index 000000000..1ba2df724
Binary files /dev/null and b/common/src/main/res/drawable-xxxhdpi/signin.9.png differ
diff --git a/common/src/main/res/drawable-xxxhdpi/signin_pressed.9.png b/common/src/main/res/drawable-xxxhdpi/signin_pressed.9.png
new file mode 100644
index 000000000..b728c3dd8
Binary files /dev/null and b/common/src/main/res/drawable-xxxhdpi/signin_pressed.9.png differ
diff --git a/common/src/main/res/drawable/score_background.xml b/common/src/main/res/drawable/score_background.xml
new file mode 100644
index 000000000..3b65eb5c9
--- /dev/null
+++ b/common/src/main/res/drawable/score_background.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/src/main/res/drawable/signin_button.xml b/common/src/main/res/drawable/signin_button.xml
new file mode 100644
index 000000000..8a61f764f
--- /dev/null
+++ b/common/src/main/res/drawable/signin_button.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/common/src/main/res/raw/ho_ho_ho.mp3 b/common/src/main/res/raw/ho_ho_ho.mp3
new file mode 100644
index 000000000..5870daae8
Binary files /dev/null and b/common/src/main/res/raw/ho_ho_ho.mp3 differ
diff --git a/common/src/main/res/values-af/strings_gamenames.xml b/common/src/main/res/values-af/strings_gamenames.xml
new file mode 100644
index 000000000..e687c26e8
--- /dev/null
+++ b/common/src/main/res/values-af/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Geheue
+ Elf-stralerpak
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Sneeubal-speletjie
+ Snowdown
+
diff --git a/common/src/main/res/values-af/strings_invite.xml b/common/src/main/res/values-af/strings_invite.xml
new file mode 100644
index 000000000..b0a98bf11
--- /dev/null
+++ b/common/src/main/res/values-af/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Nooi jou vriende om Kersvader met Google te volg
+ Probeer Santa Tracker en kyk hoe Kersvader om die wêreld vlieg!
+ Ek het %1$d aangeteken deur die %2$s-speletjie met die elwe in Santa Tracker te speel. Kan jy dit klop?
+
diff --git a/common/src/main/res/values-af/strings_jetpack.xml b/common/src/main/res/values-af/strings_jetpack.xml
new file mode 100644
index 000000000..12ee9cfe6
--- /dev/null
+++ b/common/src/main/res/values-af/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Meld aan om prestasies te ontsluit en jou telling te plaas!
+ Speel weer
+ Telling
+
diff --git a/common/src/main/res/values-ar-rXB/strings_gamenames.xml b/common/src/main/res/values-ar-rXB/strings_gamenames.xml
new file mode 100644
index 000000000..a2152d874
--- /dev/null
+++ b/common/src/main/res/values-ar-rXB/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-ar-rXB/strings_invite.xml b/common/src/main/res/values-ar-rXB/strings_invite.xml
new file mode 100644
index 000000000..92a8c2e10
--- /dev/null
+++ b/common/src/main/res/values-ar-rXB/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-ar-rXB/strings_jetpack.xml b/common/src/main/res/values-ar-rXB/strings_jetpack.xml
new file mode 100644
index 000000000..66fe7a59f
--- /dev/null
+++ b/common/src/main/res/values-ar-rXB/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-bg/strings_gamenames.xml b/common/src/main/res/values-bg/strings_gamenames.xml
new file mode 100644
index 000000000..8a7504c21
--- /dev/null
+++ b/common/src/main/res/values-bg/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Гъмбол
+ Мемори
+ Реактивно джудже
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Игра „Snowball“
+ Snowdown
+
diff --git a/common/src/main/res/values-bg/strings_invite.xml b/common/src/main/res/values-bg/strings_invite.xml
new file mode 100644
index 000000000..9c86eb1f8
--- /dev/null
+++ b/common/src/main/res/values-bg/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Поканете приятелите си да следят Дядо Коледа с Google
+ Изпробвайте „Къде е Дядо Коледа“ и гледайте как белобрадият старец лети по цял свят!
+ Постигнах %1$d точки в играта %2$s с джуджетата в приложението на Google „Къде е Дядо Коледа“. Можете ли да надминете резултата ми?
+
diff --git a/common/src/main/res/values-bg/strings_jetpack.xml b/common/src/main/res/values-bg/strings_jetpack.xml
new file mode 100644
index 000000000..79e2b5099
--- /dev/null
+++ b/common/src/main/res/values-bg/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Влезте в профила си, за да отключвате постижения и да публикувате резултата си!
+ Нова игра
+ Резултат
+
diff --git a/common/src/main/res/values-ca/strings_gamenames.xml b/common/src/main/res/values-ca/strings_gamenames.xml
new file mode 100644
index 000000000..72e11737a
--- /dev/null
+++ b/common/src/main/res/values-ca/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ El joc Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-ca/strings_invite.xml b/common/src/main/res/values-ca/strings_invite.xml
new file mode 100644
index 000000000..181621836
--- /dev/null
+++ b/common/src/main/res/values-ca/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Convideu els vostres amics a seguir el Pare Noel amb Google
+ Prova l\'aplicació Segueix el Pare Noel i mira com el Pare Noel vola per tot el món.
+ He aconseguit %1$d punts jugant amb els elfs al joc %2$s a Segueix el Pare Noel de Google. Aviam si em guanyes!
+
diff --git a/common/src/main/res/values-ca/strings_jetpack.xml b/common/src/main/res/values-ca/strings_jetpack.xml
new file mode 100644
index 000000000..b026509f8
--- /dev/null
+++ b/common/src/main/res/values-ca/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Inicieu la sessió per desbloquejar assoliments i publicar la vostra puntuació
+ Torna a jugar
+ Puntuació
+
diff --git a/common/src/main/res/values-da/strings_gamenames.xml b/common/src/main/res/values-da/strings_gamenames.xml
new file mode 100644
index 000000000..3fb1976ad
--- /dev/null
+++ b/common/src/main/res/values-da/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snekuglespil
+ Raketslædespil
+ Julemandens rensdyr
+ Sneboldspil
+ Snowdown
+
diff --git a/common/src/main/res/values-da/strings_invite.xml b/common/src/main/res/values-da/strings_invite.xml
new file mode 100644
index 000000000..b8c78e7d9
--- /dev/null
+++ b/common/src/main/res/values-da/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Inviter dine venner til at følge julemanden med Google
+ Prøv Følg julemanden, og se julemanden flyve verden rundt!
+ Jeg fik en score på %1$d, da jeg spillede %2$s-spillet sammen med nisserne i Googles Følg julemanden. Kan du slå den score?
+
diff --git a/common/src/main/res/values-da/strings_jetpack.xml b/common/src/main/res/values-da/strings_jetpack.xml
new file mode 100644
index 000000000..da62d3d56
--- /dev/null
+++ b/common/src/main/res/values-da/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Log ind for at låse op for resultater og indsende dine point.
+ Spil igen
+ Pointtal
+
diff --git a/common/src/main/res/values-de-rAT/strings_gamenames.xml b/common/src/main/res/values-de-rAT/strings_gamenames.xml
new file mode 100644
index 000000000..bc79191c0
--- /dev/null
+++ b/common/src/main/res/values-de-rAT/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball Game
+ Snowdown
+
diff --git a/common/src/main/res/values-de-rAT/strings_invite.xml b/common/src/main/res/values-de-rAT/strings_invite.xml
new file mode 100644
index 000000000..931606d08
--- /dev/null
+++ b/common/src/main/res/values-de-rAT/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Freunde dazu einladen, die Reise des Weihnachtsmanns mit Google zu verfolgen
+ Begib dich auf die Spuren des Weihnachtsmanns und verfolge ihn auf seinem Flug um die Welt!
+ Ich habe in \"Auf den Spuren des Weihnachtsmanns\" von Google das Spiel %2$s mit den Wichteln gespielt und %1$d Punkte erreicht. Schaffst du das auch?
+
diff --git a/common/src/main/res/values-de-rAT/strings_jetpack.xml b/common/src/main/res/values-de-rAT/strings_jetpack.xml
new file mode 100644
index 000000000..94ecb86e2
--- /dev/null
+++ b/common/src/main/res/values-de-rAT/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Melde dich an, um deine Erfolge zu speichern und deine Punktzahl zu posten!
+ Neues Spiel
+ Punktzahl
+
diff --git a/common/src/main/res/values-de-rCH/strings_gamenames.xml b/common/src/main/res/values-de-rCH/strings_gamenames.xml
new file mode 100644
index 000000000..bc79191c0
--- /dev/null
+++ b/common/src/main/res/values-de-rCH/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball Game
+ Snowdown
+
diff --git a/common/src/main/res/values-de-rCH/strings_invite.xml b/common/src/main/res/values-de-rCH/strings_invite.xml
new file mode 100644
index 000000000..931606d08
--- /dev/null
+++ b/common/src/main/res/values-de-rCH/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Freunde dazu einladen, die Reise des Weihnachtsmanns mit Google zu verfolgen
+ Begib dich auf die Spuren des Weihnachtsmanns und verfolge ihn auf seinem Flug um die Welt!
+ Ich habe in \"Auf den Spuren des Weihnachtsmanns\" von Google das Spiel %2$s mit den Wichteln gespielt und %1$d Punkte erreicht. Schaffst du das auch?
+
diff --git a/common/src/main/res/values-de-rCH/strings_jetpack.xml b/common/src/main/res/values-de-rCH/strings_jetpack.xml
new file mode 100644
index 000000000..94ecb86e2
--- /dev/null
+++ b/common/src/main/res/values-de-rCH/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Melde dich an, um deine Erfolge zu speichern und deine Punktzahl zu posten!
+ Neues Spiel
+ Punktzahl
+
diff --git a/common/src/main/res/values-de/strings_gamenames.xml b/common/src/main/res/values-de/strings_gamenames.xml
new file mode 100644
index 000000000..bc79191c0
--- /dev/null
+++ b/common/src/main/res/values-de/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball Game
+ Snowdown
+
diff --git a/common/src/main/res/values-de/strings_invite.xml b/common/src/main/res/values-de/strings_invite.xml
new file mode 100644
index 000000000..600b50fbd
--- /dev/null
+++ b/common/src/main/res/values-de/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Freunde dazu einladen, die Reise des Weihnachtsmanns mit Google zu verfolgen
+ Begib dich auf die Spuren des Weihnachtsmanns und verfolge ihn auf seinem Flug um die Welt!
+ Ich habe in Google Santa Tracker das Spiel %2$s mit den Wichteln gespielt und %1$d Punkte erreicht. Schaffst du das auch?
+
\ No newline at end of file
diff --git a/common/src/main/res/values-de/strings_jetpack.xml b/common/src/main/res/values-de/strings_jetpack.xml
new file mode 100644
index 000000000..94ecb86e2
--- /dev/null
+++ b/common/src/main/res/values-de/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Melde dich an, um deine Erfolge zu speichern und deine Punktzahl zu posten!
+ Neues Spiel
+ Punktzahl
+
diff --git a/common/src/main/res/values-en-rGB/strings_gamenames.xml b/common/src/main/res/values-en-rGB/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-en-rGB/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rGB/strings_invite.xml b/common/src/main/res/values-en-rGB/strings_invite.xml
new file mode 100644
index 000000000..5408f2b79
--- /dev/null
+++ b/common/src/main/res/values-en-rGB/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rGB/strings_jetpack.xml b/common/src/main/res/values-en-rGB/strings_jetpack.xml
new file mode 100644
index 000000000..428344416
--- /dev/null
+++ b/common/src/main/res/values-en-rGB/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-en-rIE/strings_gamenames.xml b/common/src/main/res/values-en-rIE/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-en-rIE/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rIE/strings_invite.xml b/common/src/main/res/values-en-rIE/strings_invite.xml
new file mode 100644
index 000000000..5408f2b79
--- /dev/null
+++ b/common/src/main/res/values-en-rIE/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rIE/strings_jetpack.xml b/common/src/main/res/values-en-rIE/strings_jetpack.xml
new file mode 100644
index 000000000..428344416
--- /dev/null
+++ b/common/src/main/res/values-en-rIE/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-en-rIN/strings_gamenames.xml b/common/src/main/res/values-en-rIN/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-en-rIN/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rIN/strings_invite.xml b/common/src/main/res/values-en-rIN/strings_invite.xml
new file mode 100644
index 000000000..5408f2b79
--- /dev/null
+++ b/common/src/main/res/values-en-rIN/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rIN/strings_jetpack.xml b/common/src/main/res/values-en-rIN/strings_jetpack.xml
new file mode 100644
index 000000000..428344416
--- /dev/null
+++ b/common/src/main/res/values-en-rIN/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-en-rSG/strings_gamenames.xml b/common/src/main/res/values-en-rSG/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-en-rSG/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rSG/strings_invite.xml b/common/src/main/res/values-en-rSG/strings_invite.xml
new file mode 100644
index 000000000..5408f2b79
--- /dev/null
+++ b/common/src/main/res/values-en-rSG/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rSG/strings_jetpack.xml b/common/src/main/res/values-en-rSG/strings_jetpack.xml
new file mode 100644
index 000000000..428344416
--- /dev/null
+++ b/common/src/main/res/values-en-rSG/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-en-rXA/strings_gamenames.xml b/common/src/main/res/values-en-rXA/strings_gamenames.xml
new file mode 100644
index 000000000..785f7e120
--- /dev/null
+++ b/common/src/main/res/values-en-rXA/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ [Ĝûmбåļļ one]
+ [Mémöŕý one]
+ [Éļƒ Ĵéţþåçķ one two]
+ [Šñöŵĝļöбé one two]
+ [Ŕöçķéţ Šļéîĝĥ one two]
+ [Ðåšĥéŕ Ðåñçéŕ one two]
+ [Šñöŵбåļļ ĝåmé one two]
+ [Šñöŵðöŵñ one]
+
diff --git a/common/src/main/res/values-en-rXA/strings_invite.xml b/common/src/main/res/values-en-rXA/strings_invite.xml
new file mode 100644
index 000000000..f38662bd2
--- /dev/null
+++ b/common/src/main/res/values-en-rXA/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ [Îñvîţé ýöûŕ ƒŕîéñðš ţö ţŕåçķ Šåñţå ŵîţĥ Ĝööĝļé one two three four five six seven eight nine ten]
+ [Ţŕý Šåñţå Ţŕåçķéŕ åñð ŵåţçĥ Šåñţå ƒļý åŕöûñð ţĥé ŵöŕļð¡ one two three four five six seven eight nine ten eleven]
+ [Î šçöŕéð ᐅ%1$dᐊ þļåýîñĝ ţĥé ᐅ%2$sᐊ ĝåmé ŵîţĥ ţĥé éļvéš îñ Ĝööĝļé\'š Šåñţå Ţŕåçķéŕ, çåñ ýöû бéåţ ţĥåţ¿ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen]
+
diff --git a/common/src/main/res/values-en-rXA/strings_jetpack.xml b/common/src/main/res/values-en-rXA/strings_jetpack.xml
new file mode 100644
index 000000000..44c302cb8
--- /dev/null
+++ b/common/src/main/res/values-en-rXA/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ [Šîĝñ îñ ţö ûñļöçķ åçĥîévéméñţš åñð þöšţ ýöûŕ šçöŕé¡ one two three four five six seven eight nine ten eleven]
+ [Þļåý Åĝåîñ one two]
+ [Šçöŕé one]
+
diff --git a/common/src/main/res/values-en-rXC/strings_gamenames.xml b/common/src/main/res/values-en-rXC/strings_gamenames.xml
new file mode 100644
index 000000000..c19c7710e
--- /dev/null
+++ b/common/src/main/res/values-en-rXC/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rXC/strings_invite.xml b/common/src/main/res/values-en-rXC/strings_invite.xml
new file mode 100644
index 000000000..7191414d5
--- /dev/null
+++ b/common/src/main/res/values-en-rXC/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rXC/strings_jetpack.xml b/common/src/main/res/values-en-rXC/strings_jetpack.xml
new file mode 100644
index 000000000..8ea177340
--- /dev/null
+++ b/common/src/main/res/values-en-rXC/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-en-rZA/strings_gamenames.xml b/common/src/main/res/values-en-rZA/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-en-rZA/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-en-rZA/strings_invite.xml b/common/src/main/res/values-en-rZA/strings_invite.xml
new file mode 100644
index 000000000..5408f2b79
--- /dev/null
+++ b/common/src/main/res/values-en-rZA/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite your friends to track Santa with Google
+ Try Santa Tracker and watch Santa fly around the world!
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
diff --git a/common/src/main/res/values-en-rZA/strings_jetpack.xml b/common/src/main/res/values-en-rZA/strings_jetpack.xml
new file mode 100644
index 000000000..428344416
--- /dev/null
+++ b/common/src/main/res/values-en-rZA/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/values-es-rAR/strings_gamenames.xml b/common/src/main/res/values-es-rAR/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rAR/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rAR/strings_invite.xml b/common/src/main/res/values-es-rAR/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rAR/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rAR/strings_jetpack.xml b/common/src/main/res/values-es-rAR/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rAR/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rBO/strings_gamenames.xml b/common/src/main/res/values-es-rBO/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rBO/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rBO/strings_invite.xml b/common/src/main/res/values-es-rBO/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rBO/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rBO/strings_jetpack.xml b/common/src/main/res/values-es-rBO/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rBO/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rCL/strings_gamenames.xml b/common/src/main/res/values-es-rCL/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rCL/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rCL/strings_invite.xml b/common/src/main/res/values-es-rCL/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rCL/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rCL/strings_jetpack.xml b/common/src/main/res/values-es-rCL/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rCL/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rCO/strings_gamenames.xml b/common/src/main/res/values-es-rCO/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rCO/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rCO/strings_invite.xml b/common/src/main/res/values-es-rCO/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rCO/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rCO/strings_jetpack.xml b/common/src/main/res/values-es-rCO/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rCO/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rCR/strings_gamenames.xml b/common/src/main/res/values-es-rCR/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rCR/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rCR/strings_invite.xml b/common/src/main/res/values-es-rCR/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rCR/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rCR/strings_jetpack.xml b/common/src/main/res/values-es-rCR/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rCR/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rDO/strings_gamenames.xml b/common/src/main/res/values-es-rDO/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rDO/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rDO/strings_invite.xml b/common/src/main/res/values-es-rDO/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rDO/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rDO/strings_jetpack.xml b/common/src/main/res/values-es-rDO/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rDO/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rEC/strings_gamenames.xml b/common/src/main/res/values-es-rEC/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rEC/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rEC/strings_invite.xml b/common/src/main/res/values-es-rEC/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rEC/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rEC/strings_jetpack.xml b/common/src/main/res/values-es-rEC/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rEC/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rES/strings_gamenames.xml b/common/src/main/res/values-es-rES/strings_gamenames.xml
new file mode 100755
index 000000000..386cbd95f
--- /dev/null
+++ b/common/src/main/res/values-es-rES/strings_gamenames.xml
@@ -0,0 +1,10 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+
diff --git a/common/src/main/res/values-es-rES/strings_jetpack.xml b/common/src/main/res/values-es-rES/strings_jetpack.xml
new file mode 100644
index 000000000..b770d7fa7
--- /dev/null
+++ b/common/src/main/res/values-es-rES/strings_jetpack.xml
@@ -0,0 +1,5 @@
+
+ Accede a tu cuenta para desbloquear logros y publicar tu puntuación
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rGT/strings_gamenames.xml b/common/src/main/res/values-es-rGT/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rGT/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rGT/strings_invite.xml b/common/src/main/res/values-es-rGT/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rGT/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rGT/strings_jetpack.xml b/common/src/main/res/values-es-rGT/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rGT/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rHN/strings_gamenames.xml b/common/src/main/res/values-es-rHN/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rHN/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rHN/strings_invite.xml b/common/src/main/res/values-es-rHN/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rHN/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rHN/strings_jetpack.xml b/common/src/main/res/values-es-rHN/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rHN/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rMX/strings_gamenames.xml b/common/src/main/res/values-es-rMX/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rMX/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rMX/strings_invite.xml b/common/src/main/res/values-es-rMX/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rMX/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rMX/strings_jetpack.xml b/common/src/main/res/values-es-rMX/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rMX/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rNI/strings_gamenames.xml b/common/src/main/res/values-es-rNI/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rNI/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rNI/strings_invite.xml b/common/src/main/res/values-es-rNI/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rNI/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rNI/strings_jetpack.xml b/common/src/main/res/values-es-rNI/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rNI/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rPA/strings_gamenames.xml b/common/src/main/res/values-es-rPA/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rPA/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rPA/strings_invite.xml b/common/src/main/res/values-es-rPA/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rPA/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rPA/strings_jetpack.xml b/common/src/main/res/values-es-rPA/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rPA/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rPE/strings_gamenames.xml b/common/src/main/res/values-es-rPE/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rPE/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rPE/strings_invite.xml b/common/src/main/res/values-es-rPE/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rPE/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rPE/strings_jetpack.xml b/common/src/main/res/values-es-rPE/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rPE/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rPR/strings_gamenames.xml b/common/src/main/res/values-es-rPR/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rPR/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rPR/strings_invite.xml b/common/src/main/res/values-es-rPR/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rPR/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rPR/strings_jetpack.xml b/common/src/main/res/values-es-rPR/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rPR/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rPY/strings_gamenames.xml b/common/src/main/res/values-es-rPY/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rPY/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rPY/strings_invite.xml b/common/src/main/res/values-es-rPY/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rPY/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rPY/strings_jetpack.xml b/common/src/main/res/values-es-rPY/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rPY/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rSV/strings_gamenames.xml b/common/src/main/res/values-es-rSV/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rSV/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rSV/strings_invite.xml b/common/src/main/res/values-es-rSV/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rSV/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rSV/strings_jetpack.xml b/common/src/main/res/values-es-rSV/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rSV/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rUS/strings_gamenames.xml b/common/src/main/res/values-es-rUS/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rUS/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rUS/strings_invite.xml b/common/src/main/res/values-es-rUS/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rUS/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rUS/strings_jetpack.xml b/common/src/main/res/values-es-rUS/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rUS/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rUY/strings_gamenames.xml b/common/src/main/res/values-es-rUY/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rUY/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rUY/strings_invite.xml b/common/src/main/res/values-es-rUY/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rUY/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rUY/strings_jetpack.xml b/common/src/main/res/values-es-rUY/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rUY/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es-rVE/strings_gamenames.xml b/common/src/main/res/values-es-rVE/strings_gamenames.xml
new file mode 100644
index 000000000..844aca537
--- /dev/null
+++ b/common/src/main/res/values-es-rVE/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-es-rVE/strings_invite.xml b/common/src/main/res/values-es-rVE/strings_invite.xml
new file mode 100644
index 000000000..74d876bc9
--- /dev/null
+++ b/common/src/main/res/values-es-rVE/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Santa con Google
+ Prueba la app de Sigue a Santa y mira cómo vuela por todo el mundo.
+ Mi puntuación es de %1$d en el juego %2$s con los duendes en Sigue a Santa de Google. ¿Puedes vencerme?
+
diff --git a/common/src/main/res/values-es-rVE/strings_jetpack.xml b/common/src/main/res/values-es-rVE/strings_jetpack.xml
new file mode 100644
index 000000000..d30a26cd7
--- /dev/null
+++ b/common/src/main/res/values-es-rVE/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ¡Accede para desbloquear logros y publicar tu puntuación!
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-es/strings_gamenames.xml b/common/src/main/res/values-es/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-es/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-es/strings_invite.xml b/common/src/main/res/values-es/strings_invite.xml
new file mode 100644
index 000000000..b7565d9ec
--- /dev/null
+++ b/common/src/main/res/values-es/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita a tus amigos a seguir a Papá Noel con Google
+ ¡Prueba Sigue a Papá Noel y acompáñalo virtualmente por todo el mundo!
+ He conseguido %1$d puntos jugando a %2$s con los elfos en Seguir a Papá Noel de Google. ¡A ver si me ganas!
+
diff --git a/common/src/main/res/values-es/strings_jetpack.xml b/common/src/main/res/values-es/strings_jetpack.xml
new file mode 100644
index 000000000..5328045ee
--- /dev/null
+++ b/common/src/main/res/values-es/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Inicia sesión para desbloquear logros y publicar tu puntuación.
+ Volver a jugar
+ Puntuación
+
diff --git a/common/src/main/res/values-et/strings_gamenames.xml b/common/src/main/res/values-et/strings_gamenames.xml
new file mode 100644
index 000000000..89f4e4405
--- /dev/null
+++ b/common/src/main/res/values-et/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Nätsupall
+ Mälu
+ Haldja rakettranits
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball\'i mäng
+ Snowdown
+
diff --git a/common/src/main/res/values-et/strings_invite.xml b/common/src/main/res/values-et/strings_invite.xml
new file mode 100644
index 000000000..215ed9f4a
--- /dev/null
+++ b/common/src/main/res/values-et/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Kutsuge sõbrad Google\'iga jõuluvana jälgima
+ Proovige rakendust Santa Tracker ja vaadake, kuidas jõuluvana ümber maailma lendab!
+ Sain Google\'i jõuluvana jälgimise rakenduses päkapikkudega mängu %2$s mängides %1$d punkti. Arvad, et oled osavam?
+
diff --git a/common/src/main/res/values-et/strings_jetpack.xml b/common/src/main/res/values-et/strings_jetpack.xml
new file mode 100644
index 000000000..ba21b662f
--- /dev/null
+++ b/common/src/main/res/values-et/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Registreeruge, et saavutusi püüda ja oma skoore postitada!
+ Mängi uuesti
+ Skoor
+
diff --git a/common/src/main/res/values-fi/strings_gamenames.xml b/common/src/main/res/values-fi/strings_gamenames.xml
new file mode 100644
index 000000000..38358085d
--- /dev/null
+++ b/common/src/main/res/values-fi/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Muistipeli
+ Tontturakettireppu
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball-peli
+ Snowdown
+
diff --git a/common/src/main/res/values-fi/strings_invite.xml b/common/src/main/res/values-fi/strings_invite.xml
new file mode 100644
index 000000000..d3a2abe95
--- /dev/null
+++ b/common/src/main/res/values-fi/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Kutsu ystäväsi seuraamaan pukin matkaa Googlen kanssa
+ Kokeile Santa Trackeria ja seuraa pukin matkaa maailman ympäri.
+ Sain pelissä %2$s tuloksen %1$d, kun pelasin tonttujen kanssa Googlen Joulupukin jäljitintä. Pystytkö parempaan?
+
diff --git a/common/src/main/res/values-fi/strings_jetpack.xml b/common/src/main/res/values-fi/strings_jetpack.xml
new file mode 100644
index 000000000..a77b95cd5
--- /dev/null
+++ b/common/src/main/res/values-fi/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Kirjaudu sisään, niin voit avata saavutuksia ja julkaista tuloksesi.
+ Pelaa uudelleen
+ Pisteet
+
diff --git a/common/src/main/res/values-fil/strings_gamenames.xml b/common/src/main/res/values-fil/strings_gamenames.xml
new file mode 100644
index 000000000..53e834857
--- /dev/null
+++ b/common/src/main/res/values-fil/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Larong Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-fil/strings_invite.xml b/common/src/main/res/values-fil/strings_invite.xml
new file mode 100644
index 000000000..ad736646d
--- /dev/null
+++ b/common/src/main/res/values-fil/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Imbitahan ang iyong mga kaibigan na subaybayan si Santa sa Google
+ Subukan ang Santa Tracker at panoorin ang paglipad ni Santa sa buong mundo!
+ Nakapuntos ako ng %1$d sa paglalaro ng %2$s game na may elves sa Santa Tracker ng Google, kaya mo bang talunin iyon?
+
diff --git a/common/src/main/res/values-fil/strings_jetpack.xml b/common/src/main/res/values-fil/strings_jetpack.xml
new file mode 100644
index 000000000..0723cba66
--- /dev/null
+++ b/common/src/main/res/values-fil/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Mag-sign in upang mai-unlock ang mga achievement at mai-post ang iyong score!
+ Maglaro Muli
+ Score
+
diff --git a/common/src/main/res/values-fr-rCA/strings_gamenames.xml b/common/src/main/res/values-fr-rCA/strings_gamenames.xml
new file mode 100644
index 000000000..8a261a833
--- /dev/null
+++ b/common/src/main/res/values-fr-rCA/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jeu Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-fr-rCA/strings_invite.xml b/common/src/main/res/values-fr-rCA/strings_invite.xml
new file mode 100644
index 000000000..741c77da5
--- /dev/null
+++ b/common/src/main/res/values-fr-rCA/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invite tes amis à suivre le père Noël avec Google
+ Essayez l\'application Sur les traces du père Noël et suivez le père Noël à travers le monde!
+ J\'ai obtenu un score de %1$d en jouant à %2$s avec les lutins dans Sur les traces du père Noël de Google, peux-tu faire mieux?
+
diff --git a/common/src/main/res/values-fr-rCA/strings_jetpack.xml b/common/src/main/res/values-fr-rCA/strings_jetpack.xml
new file mode 100644
index 000000000..cf7a0fed6
--- /dev/null
+++ b/common/src/main/res/values-fr-rCA/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Connectez-vous pour débloquer vos réussites et afficher votre score!
+ Jouer de nouveau
+ Score
+
diff --git a/common/src/main/res/values-fr-rCH/strings_gamenames.xml b/common/src/main/res/values-fr-rCH/strings_gamenames.xml
new file mode 100644
index 000000000..97d7d7ef0
--- /dev/null
+++ b/common/src/main/res/values-fr-rCH/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jeu Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-fr-rCH/strings_invite.xml b/common/src/main/res/values-fr-rCH/strings_invite.xml
new file mode 100644
index 000000000..684b63131
--- /dev/null
+++ b/common/src/main/res/values-fr-rCH/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invitez vos amis à suivre le père Noël avec Google
+ Essaie l\'application Santa Tracker pour suivre la tournée du père Noël à travers le monde !
+ J\'ai atteint un score de %1$d points au jeu %2$s avec les lutins du programme Santa Tracker de Google. Parviendras-tu à me battre ?
+
diff --git a/common/src/main/res/values-fr-rCH/strings_jetpack.xml b/common/src/main/res/values-fr-rCH/strings_jetpack.xml
new file mode 100644
index 000000000..a28ff4471
--- /dev/null
+++ b/common/src/main/res/values-fr-rCH/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Connectez-vous pour débloquer vos exploits et publier vos scores !
+ Rejouer
+ Résultats
+
diff --git a/common/src/main/res/values-fr/strings_gamenames.xml b/common/src/main/res/values-fr/strings_gamenames.xml
new file mode 100644
index 000000000..97d7d7ef0
--- /dev/null
+++ b/common/src/main/res/values-fr/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jeu Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-fr/strings_invite.xml b/common/src/main/res/values-fr/strings_invite.xml
new file mode 100644
index 000000000..684b63131
--- /dev/null
+++ b/common/src/main/res/values-fr/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invitez vos amis à suivre le père Noël avec Google
+ Essaie l\'application Santa Tracker pour suivre la tournée du père Noël à travers le monde !
+ J\'ai atteint un score de %1$d points au jeu %2$s avec les lutins du programme Santa Tracker de Google. Parviendras-tu à me battre ?
+
diff --git a/common/src/main/res/values-fr/strings_jetpack.xml b/common/src/main/res/values-fr/strings_jetpack.xml
new file mode 100644
index 000000000..a28ff4471
--- /dev/null
+++ b/common/src/main/res/values-fr/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Connectez-vous pour débloquer vos exploits et publier vos scores !
+ Rejouer
+ Résultats
+
diff --git a/common/src/main/res/values-gsw/strings_gamenames.xml b/common/src/main/res/values-gsw/strings_gamenames.xml
new file mode 100644
index 000000000..bc79191c0
--- /dev/null
+++ b/common/src/main/res/values-gsw/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball Game
+ Snowdown
+
diff --git a/common/src/main/res/values-gsw/strings_invite.xml b/common/src/main/res/values-gsw/strings_invite.xml
new file mode 100644
index 000000000..931606d08
--- /dev/null
+++ b/common/src/main/res/values-gsw/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Freunde dazu einladen, die Reise des Weihnachtsmanns mit Google zu verfolgen
+ Begib dich auf die Spuren des Weihnachtsmanns und verfolge ihn auf seinem Flug um die Welt!
+ Ich habe in \"Auf den Spuren des Weihnachtsmanns\" von Google das Spiel %2$s mit den Wichteln gespielt und %1$d Punkte erreicht. Schaffst du das auch?
+
diff --git a/common/src/main/res/values-gsw/strings_jetpack.xml b/common/src/main/res/values-gsw/strings_jetpack.xml
new file mode 100644
index 000000000..94ecb86e2
--- /dev/null
+++ b/common/src/main/res/values-gsw/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Melde dich an, um deine Erfolge zu speichern und deine Punktzahl zu posten!
+ Neues Spiel
+ Punktzahl
+
diff --git a/common/src/main/res/values-hdpi/dimens.xml b/common/src/main/res/values-hdpi/dimens.xml
new file mode 100644
index 000000000..800ae1042
--- /dev/null
+++ b/common/src/main/res/values-hdpi/dimens.xml
@@ -0,0 +1,7 @@
+
+
+ 8dp
+ 45dp
+ 16dp
+
+
diff --git a/common/src/main/res/values-hr/strings_gamenames.xml b/common/src/main/res/values-hr/strings_gamenames.xml
new file mode 100644
index 000000000..02b472a3f
--- /dev/null
+++ b/common/src/main/res/values-hr/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Igra pamćenja
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Igra Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-hr/strings_invite.xml b/common/src/main/res/values-hr/strings_invite.xml
new file mode 100644
index 000000000..ad350bc43
--- /dev/null
+++ b/common/src/main/res/values-hr/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Pozovite prijatelje i zajedno pratite Djeda Božićnjaka uz Google
+ Isprobajte igru Slijedi Djeda Božićnjaka i pratite Djedicu u njegovu letu širom svijeta!
+ Moj rezultat u igri %2$s s vilenjacima u Slijedi Djeda Božićnjaka: %1$d. Misliš da možeš bolje?
+
diff --git a/common/src/main/res/values-hr/strings_jetpack.xml b/common/src/main/res/values-hr/strings_jetpack.xml
new file mode 100644
index 000000000..df14efe57
--- /dev/null
+++ b/common/src/main/res/values-hr/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Prijavite se da biste otključali uspjehe i objavili rezultat!
+ Igraj ponovo
+ Rezultat
+
diff --git a/common/src/main/res/values-id/strings_gamenames.xml b/common/src/main/res/values-id/strings_gamenames.xml
new file mode 100644
index 000000000..c99fae438
--- /dev/null
+++ b/common/src/main/res/values-id/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Game Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-id/strings_invite.xml b/common/src/main/res/values-id/strings_invite.xml
new file mode 100644
index 000000000..16abd10be
--- /dev/null
+++ b/common/src/main/res/values-id/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Undang teman Anda untuk melacak Sinterklas dengan Google
+ Coba Pelacak Sinterklas dan tonton Sinterklas terbang ke seluruh dunia!
+ Saya memperoleh skor %1$d saat bermain game %2$s dengan para kurcaci di Google Pelacak Sinterklas. Anda bisa mengalahkannya?
+
diff --git a/common/src/main/res/values-id/strings_jetpack.xml b/common/src/main/res/values-id/strings_jetpack.xml
new file mode 100644
index 000000000..ee61b829c
--- /dev/null
+++ b/common/src/main/res/values-id/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Masuk untuk membuka pencapaian dan mengeposkan skor!
+ Main Lagi
+ Skor
+
diff --git a/common/src/main/res/values-in/strings_gamenames.xml b/common/src/main/res/values-in/strings_gamenames.xml
new file mode 100644
index 000000000..c99fae438
--- /dev/null
+++ b/common/src/main/res/values-in/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Game Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-in/strings_invite.xml b/common/src/main/res/values-in/strings_invite.xml
new file mode 100644
index 000000000..16abd10be
--- /dev/null
+++ b/common/src/main/res/values-in/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Undang teman Anda untuk melacak Sinterklas dengan Google
+ Coba Pelacak Sinterklas dan tonton Sinterklas terbang ke seluruh dunia!
+ Saya memperoleh skor %1$d saat bermain game %2$s dengan para kurcaci di Google Pelacak Sinterklas. Anda bisa mengalahkannya?
+
diff --git a/common/src/main/res/values-in/strings_jetpack.xml b/common/src/main/res/values-in/strings_jetpack.xml
new file mode 100644
index 000000000..ee61b829c
--- /dev/null
+++ b/common/src/main/res/values-in/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Masuk untuk membuka pencapaian dan mengeposkan skor!
+ Main Lagi
+ Skor
+
diff --git a/common/src/main/res/values-it/strings_gamenames.xml b/common/src/main/res/values-it/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-it/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-it/strings_invite.xml b/common/src/main/res/values-it/strings_invite.xml
new file mode 100644
index 000000000..84a1141bc
--- /dev/null
+++ b/common/src/main/res/values-it/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invita i tuoi amici a seguire Babbo Natale con Google
+ Prova l\'app Segui Babbo Natale e guardalo mentre vola intorno al mondo.
+ Ho totalizzato %1$d punti a %2$s con i folletti di Segui Babbo Natale di Google. Puoi fare di meglio?
+
diff --git a/common/src/main/res/values-it/strings_jetpack.xml b/common/src/main/res/values-it/strings_jetpack.xml
new file mode 100644
index 000000000..9f2727738
--- /dev/null
+++ b/common/src/main/res/values-it/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Accedi per sbloccare i risultati e pubblicare il tuo punteggio.
+ Gioca ancora
+ Punteggio
+
diff --git a/common/src/main/res/values-ja/strings_gamenames.xml b/common/src/main/res/values-ja/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-ja/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-ja/strings_invite.xml b/common/src/main/res/values-ja/strings_invite.xml
new file mode 100644
index 000000000..10f3862cc
--- /dev/null
+++ b/common/src/main/res/values-ja/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Google の「サンタを追いかけよう」に友だちを招待
+ 「サンタを追いかけよう」で世界中を飛び回るサンタさんを見よう!
+ Google の「サンタを追いかけよう」で、妖精たちと %2$s ゲームをして %1$d 点達成!あなたもチャレンジしませんか?
+
diff --git a/common/src/main/res/values-ja/strings_jetpack.xml b/common/src/main/res/values-ja/strings_jetpack.xml
new file mode 100644
index 000000000..05f6fb0a7
--- /dev/null
+++ b/common/src/main/res/values-ja/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ログインしたら、結果を開放してスコアを投稿しよう!
+ もう一度プレイ
+ スコア
+
diff --git a/common/src/main/res/values-ko/strings_gamenames.xml b/common/src/main/res/values-ko/strings_gamenames.xml
new file mode 100644
index 000000000..2865e0004
--- /dev/null
+++ b/common/src/main/res/values-ko/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ 풍선껌
+ 기억력
+ 제트 추진 엘프
+ 스노우글로브
+ 로켓 썰매
+ 대셔 댄서
+ 눈싸움 게임
+ 최후의 눈싸움 결전
+
diff --git a/common/src/main/res/values-ko/strings_invite.xml b/common/src/main/res/values-ko/strings_invite.xml
new file mode 100644
index 000000000..a84a61ddb
--- /dev/null
+++ b/common/src/main/res/values-ko/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ 친구를 초대하여 Google에서 산타 추적하기
+ 산타 추적기를 사용해 전 세계를 도는 산타의 여정을 지켜봐 주세요.
+ Google 산타 추적기에서 엘프 친구들과 함께 %2$s에서 %1$d점을 득점했어요. 따라잡을 테면 따라잡아 보세요.
+
diff --git a/common/src/main/res/values-ko/strings_jetpack.xml b/common/src/main/res/values-ko/strings_jetpack.xml
new file mode 100644
index 000000000..0919deb72
--- /dev/null
+++ b/common/src/main/res/values-ko/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ 로그인하여 업적을 달성하고 점수를 게시해 보세요.
+ 다시 하기
+ 점수
+
diff --git a/common/src/main/res/values-ln/strings_gamenames.xml b/common/src/main/res/values-ln/strings_gamenames.xml
new file mode 100644
index 000000000..97d7d7ef0
--- /dev/null
+++ b/common/src/main/res/values-ln/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jeu Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-ln/strings_invite.xml b/common/src/main/res/values-ln/strings_invite.xml
new file mode 100644
index 000000000..684b63131
--- /dev/null
+++ b/common/src/main/res/values-ln/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invitez vos amis à suivre le père Noël avec Google
+ Essaie l\'application Santa Tracker pour suivre la tournée du père Noël à travers le monde !
+ J\'ai atteint un score de %1$d points au jeu %2$s avec les lutins du programme Santa Tracker de Google. Parviendras-tu à me battre ?
+
diff --git a/common/src/main/res/values-ln/strings_jetpack.xml b/common/src/main/res/values-ln/strings_jetpack.xml
new file mode 100644
index 000000000..a28ff4471
--- /dev/null
+++ b/common/src/main/res/values-ln/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Connectez-vous pour débloquer vos exploits et publier vos scores !
+ Rejouer
+ Résultats
+
diff --git a/common/src/main/res/values-lt/strings_gamenames.xml b/common/src/main/res/values-lt/strings_gamenames.xml
new file mode 100644
index 000000000..1ac5dea40
--- /dev/null
+++ b/common/src/main/res/values-lt/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Žaidimas „Snowball“
+ Snowdown
+
diff --git a/common/src/main/res/values-lt/strings_invite.xml b/common/src/main/res/values-lt/strings_invite.xml
new file mode 100644
index 000000000..cb43cba44
--- /dev/null
+++ b/common/src/main/res/values-lt/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Pakvieskite draugus stebėti Kalėdų Senelį „Google“
+ Išbandykite „Santa Tracker“ ir pamatysite, kaip Kalėdų Senelis skrenda per pasaulį!
+ Žaisdamas (-a) %2$s su elfais programoje „Google“ Kalėdų Senelio kelionė pelniau %1$d. Galite geriau?
+
diff --git a/common/src/main/res/values-lt/strings_jetpack.xml b/common/src/main/res/values-lt/strings_jetpack.xml
new file mode 100644
index 000000000..ce384530a
--- /dev/null
+++ b/common/src/main/res/values-lt/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Prisijunkite, jei norite atrakinti laimėjimus ir paskelbti savo rezultatą!
+ Žaisti dar kartą
+ Rezultatas
+
diff --git a/common/src/main/res/values-lv/strings_gamenames.xml b/common/src/main/res/values-lv/strings_gamenames.xml
new file mode 100644
index 000000000..1242da71d
--- /dev/null
+++ b/common/src/main/res/values-lv/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball spēle
+ Atmiņas spēle
+ Elf Jetpack spēle
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-lv/strings_invite.xml b/common/src/main/res/values-lv/strings_invite.xml
new file mode 100644
index 000000000..8854d77bc
--- /dev/null
+++ b/common/src/main/res/values-lv/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Uzaicinājums draugiem sekot Ziemassvētku vecītim pakalpojumā Google
+ Izmēģiniet Ziemassvētku vecīša ceļojumu un skatieties, kā Ziemassvētku vecītis aplido apkārt visai pasaulei!
+ Es ieguvu %1$d punktus, spēlējot spēli %2$s ar elfiem Google Ziemassvētku vecīša ceļojumā. Vai varat to pārspēt?
+
diff --git a/common/src/main/res/values-lv/strings_jetpack.xml b/common/src/main/res/values-lv/strings_jetpack.xml
new file mode 100644
index 000000000..3fed2efa9
--- /dev/null
+++ b/common/src/main/res/values-lv/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Pierakstieties, lai atbloķētu sasniegumus un izliktu savu rezultātu!
+ Spēlēt vēlreiz
+ Rezultāts
+
diff --git a/common/src/main/res/values-ml/strings_gamenames.xml b/common/src/main/res/values-ml/strings_gamenames.xml
new file mode 100644
index 000000000..74de7aee7
--- /dev/null
+++ b/common/src/main/res/values-ml/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ ഗംബോൾ
+ മെമ്മറി
+ എൽഫ് ജെറ്റ്പാക്ക്
+ സ്നോഗ്ലോബ്
+ റോക്കറ്റ് ഹിമവണ്ടി
+ ഡാഷർ ഡാൻസർ
+ സ്നോബോൾ ഗെയിം
+ സ്നോഡൗൺ
+
diff --git a/common/src/main/res/values-ml/strings_invite.xml b/common/src/main/res/values-ml/strings_invite.xml
new file mode 100644
index 000000000..c54c246e9
--- /dev/null
+++ b/common/src/main/res/values-ml/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Google വഴി സാന്തയെ ട്രാക്കുചെയ്യുന്നതിന് സുഹൃത്തുക്കളെ ക്ഷണിക്കുക
+ സാന്ത ട്രാക്കർ പരീക്ഷിച്ചുകൊണ്ട് ലോകമെമ്പാടുമുള്ള സാന്തയുടെ സഞ്ചാരം കാണുക!
+ Google-ന്റെ സാന്ത ട്രാക്കറിൽ കുട്ടിച്ചാത്തന്മാരോടൊപ്പം %2$s ഗെയിം കളിച്ച് ഞാൻ %1$d സ്കോർ ചെയ്തു, നിങ്ങൾക്കത് മറികടക്കാനാകുമോ?
+
diff --git a/common/src/main/res/values-ml/strings_jetpack.xml b/common/src/main/res/values-ml/strings_jetpack.xml
new file mode 100644
index 000000000..fb73aba23
--- /dev/null
+++ b/common/src/main/res/values-ml/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ നേട്ടങ്ങൾ അൺലോക്കുചെയ്യാനും നിങ്ങളുടെ സ്കോർ പോസ്റ്റ് ചെയ്യാനും സൈൻ ഇൻ ചെയ്യുക!
+ വീണ്ടും കളിക്കുക
+ സ്കോർ
+
diff --git a/common/src/main/res/values-mo/strings_gamenames.xml b/common/src/main/res/values-mo/strings_gamenames.xml
new file mode 100644
index 000000000..41cc42d93
--- /dev/null
+++ b/common/src/main/res/values-mo/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jocul Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-mo/strings_invite.xml b/common/src/main/res/values-mo/strings_invite.xml
new file mode 100644
index 000000000..2fa2ba7f3
--- /dev/null
+++ b/common/src/main/res/values-mo/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invitați-vă prietenii să-l urmărească pe Moș Crăciun cu Google
+ Încercați Pe urmele lui Moș Crăciun și urmăriți-l pe Moș Crăciun cum călătorește în întreaga lume!
+ Am obținut un scor de %1$d jucând %2$s cu elfii, în Pe urmele lui Moș Crăciun de la Google. Mă puteți întrece?
+
diff --git a/common/src/main/res/values-mo/strings_jetpack.xml b/common/src/main/res/values-mo/strings_jetpack.xml
new file mode 100644
index 000000000..909e54286
--- /dev/null
+++ b/common/src/main/res/values-mo/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Conectați-vă pentru a debloca realizările și pentru a vă posta scorul!
+ Jucați din nou
+ Scor
+
diff --git a/common/src/main/res/values-nb/strings_gamenames.xml b/common/src/main/res/values-nb/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-nb/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-nb/strings_invite.xml b/common/src/main/res/values-nb/strings_invite.xml
new file mode 100644
index 000000000..06fb2db7a
--- /dev/null
+++ b/common/src/main/res/values-nb/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Inviter vennene dine til å følge julenissen med Google
+ Følg julenissen, og se julenissen fly verden rundt!
+ Jeg fikk %1$d poeng i %2$s-spillet med alvene i Følg julenissen fra Google. Kan du slå det?
+
diff --git a/common/src/main/res/values-nb/strings_jetpack.xml b/common/src/main/res/values-nb/strings_jetpack.xml
new file mode 100644
index 000000000..23283e7ef
--- /dev/null
+++ b/common/src/main/res/values-nb/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Logg på for å låse opp prestasjoner og legge ut poengsummen din.
+ Spill igjen
+ Poeng
+
diff --git a/common/src/main/res/values-no/strings_gamenames.xml b/common/src/main/res/values-no/strings_gamenames.xml
new file mode 100644
index 000000000..02d7c389b
--- /dev/null
+++ b/common/src/main/res/values-no/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-no/strings_invite.xml b/common/src/main/res/values-no/strings_invite.xml
new file mode 100644
index 000000000..06fb2db7a
--- /dev/null
+++ b/common/src/main/res/values-no/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Inviter vennene dine til å følge julenissen med Google
+ Følg julenissen, og se julenissen fly verden rundt!
+ Jeg fikk %1$d poeng i %2$s-spillet med alvene i Følg julenissen fra Google. Kan du slå det?
+
diff --git a/common/src/main/res/values-no/strings_jetpack.xml b/common/src/main/res/values-no/strings_jetpack.xml
new file mode 100644
index 000000000..23283e7ef
--- /dev/null
+++ b/common/src/main/res/values-no/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Logg på for å låse opp prestasjoner og legge ut poengsummen din.
+ Spill igjen
+ Poeng
+
diff --git a/common/src/main/res/values-pl/strings_gamenames.xml b/common/src/main/res/values-pl/strings_gamenames.xml
new file mode 100644
index 000000000..5104d6966
--- /dev/null
+++ b/common/src/main/res/values-pl/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Śnieżna kula
+ Sanie rakietowe
+ Fircyk i Tancerz
+ Gra Śnieżka
+ Śnieżne starcie
+
diff --git a/common/src/main/res/values-pl/strings_invite.xml b/common/src/main/res/values-pl/strings_invite.xml
new file mode 100644
index 000000000..403829439
--- /dev/null
+++ b/common/src/main/res/values-pl/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Zaproś znajomych do śledzenia Świętego Mikołaja w Google
+ Dzięki aplikacji Trasa Świętego Mikołaja zobaczysz, jak Święty Mikołaj lata po całym świecie.
+ Mój wynik w grze %2$s rozgrywanej z elfami w Google Santa Tracker to %1$d. Dasz radę mnie pokonać?
+
diff --git a/common/src/main/res/values-pl/strings_jetpack.xml b/common/src/main/res/values-pl/strings_jetpack.xml
new file mode 100644
index 000000000..a2d3c0aae
--- /dev/null
+++ b/common/src/main/res/values-pl/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Zaloguj się, aby odblokować osiągnięcia i opublikować swój wynik.
+ Zagraj jeszcze raz
+ Wynik
+
diff --git a/common/src/main/res/values-pt-rBR/strings_gamenames.xml b/common/src/main/res/values-pt-rBR/strings_gamenames.xml
new file mode 100644
index 000000000..9f9c58d34
--- /dev/null
+++ b/common/src/main/res/values-pt-rBR/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memória
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jogo Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-pt-rBR/strings_invite.xml b/common/src/main/res/values-pt-rBR/strings_invite.xml
new file mode 100644
index 000000000..c92fd0956
--- /dev/null
+++ b/common/src/main/res/values-pt-rBR/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Convide seus amigos para seguir o Papai Noel com o Google
+ Teste o Siga o Papai Noel e veja o Papai Noel voar pelo mundo!
+ Fiz %1$d pontos no jogo %2$s com os duendes no Siga o Papai Noel do Google. Você consegue fazer mais?
+
diff --git a/common/src/main/res/values-pt-rBR/strings_jetpack.xml b/common/src/main/res/values-pt-rBR/strings_jetpack.xml
new file mode 100644
index 000000000..7d64cac4a
--- /dev/null
+++ b/common/src/main/res/values-pt-rBR/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Faça login para desbloquear conquistas e postar sua pontuação.
+ Jogar novamente
+ Pontuação
+
diff --git a/common/src/main/res/values-pt-rPT/strings_gamenames.xml b/common/src/main/res/values-pt-rPT/strings_gamenames.xml
new file mode 100644
index 000000000..b991dac49
--- /dev/null
+++ b/common/src/main/res/values-pt-rPT/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memória
+ Elf Jetpack
+ Globo de Neve
+ Trenó-foguete
+ Dançarino Veloz
+ Jogo Bola de Neve
+ Snowdown
+
diff --git a/common/src/main/res/values-pt-rPT/strings_invite.xml b/common/src/main/res/values-pt-rPT/strings_invite.xml
new file mode 100644
index 000000000..b98b3a6fb
--- /dev/null
+++ b/common/src/main/res/values-pt-rPT/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Convidar os seus amigos para seguirem o Pai Natal com o Google
+ Experimenta o Localizador do Pai Natal e vê-o voar por todo o mundo!
+ Consegui uma classificação de %1$d no jogo %2$s com elfos no Localizador do Pai Natal da Google. Consegues melhor?
+
diff --git a/common/src/main/res/values-pt-rPT/strings_jetpack.xml b/common/src/main/res/values-pt-rPT/strings_jetpack.xml
new file mode 100644
index 000000000..6b795c980
--- /dev/null
+++ b/common/src/main/res/values-pt-rPT/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Inicie sessão para desbloquear conquistas e publicar a sua pontuação!
+ Jogar de novo
+ Pontuação
+
diff --git a/common/src/main/res/values-pt/strings_gamenames.xml b/common/src/main/res/values-pt/strings_gamenames.xml
new file mode 100644
index 000000000..9f9c58d34
--- /dev/null
+++ b/common/src/main/res/values-pt/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memória
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jogo Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-pt/strings_invite.xml b/common/src/main/res/values-pt/strings_invite.xml
new file mode 100644
index 000000000..c92fd0956
--- /dev/null
+++ b/common/src/main/res/values-pt/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Convide seus amigos para seguir o Papai Noel com o Google
+ Teste o Siga o Papai Noel e veja o Papai Noel voar pelo mundo!
+ Fiz %1$d pontos no jogo %2$s com os duendes no Siga o Papai Noel do Google. Você consegue fazer mais?
+
diff --git a/common/src/main/res/values-pt/strings_jetpack.xml b/common/src/main/res/values-pt/strings_jetpack.xml
new file mode 100644
index 000000000..7d64cac4a
--- /dev/null
+++ b/common/src/main/res/values-pt/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Faça login para desbloquear conquistas e postar sua pontuação.
+ Jogar novamente
+ Pontuação
+
diff --git a/common/src/main/res/values-ro/strings_gamenames.xml b/common/src/main/res/values-ro/strings_gamenames.xml
new file mode 100644
index 000000000..41cc42d93
--- /dev/null
+++ b/common/src/main/res/values-ro/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Jocul Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-ro/strings_invite.xml b/common/src/main/res/values-ro/strings_invite.xml
new file mode 100644
index 000000000..2fa2ba7f3
--- /dev/null
+++ b/common/src/main/res/values-ro/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Invitați-vă prietenii să-l urmărească pe Moș Crăciun cu Google
+ Încercați Pe urmele lui Moș Crăciun și urmăriți-l pe Moș Crăciun cum călătorește în întreaga lume!
+ Am obținut un scor de %1$d jucând %2$s cu elfii, în Pe urmele lui Moș Crăciun de la Google. Mă puteți întrece?
+
diff --git a/common/src/main/res/values-ro/strings_jetpack.xml b/common/src/main/res/values-ro/strings_jetpack.xml
new file mode 100644
index 000000000..909e54286
--- /dev/null
+++ b/common/src/main/res/values-ro/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Conectați-vă pentru a debloca realizările și pentru a vă posta scorul!
+ Jucați din nou
+ Scor
+
diff --git a/common/src/main/res/values-ru/strings_gamenames.xml b/common/src/main/res/values-ru/strings_gamenames.xml
new file mode 100644
index 000000000..f3557ddb9
--- /dev/null
+++ b/common/src/main/res/values-ru/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Жвачка
+ Память
+ Летающий эльф
+ Снежный шар
+ Реактивные сани
+ Лучший танцор
+ Снежок
+ Снежная битва
+
diff --git a/common/src/main/res/values-ru/strings_invite.xml b/common/src/main/res/values-ru/strings_invite.xml
new file mode 100644
index 000000000..9fe6762c9
--- /dev/null
+++ b/common/src/main/res/values-ru/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Пригласите друзей скачать приложение
+ Скачай приложение \"Радар Санта-Клауса\" и следи за путешествиями Санты.
+ Мой результат в игре %2$s – %1$d очков! Сможешь набрать больше?
+
diff --git a/common/src/main/res/values-ru/strings_jetpack.xml b/common/src/main/res/values-ru/strings_jetpack.xml
new file mode 100644
index 000000000..0f4eeeea4
--- /dev/null
+++ b/common/src/main/res/values-ru/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Хотите получать награды и делиться результатами с друзьями? Войдите в аккаунт!
+ Сыграть ещё раз
+ Счет
+
diff --git a/common/src/main/res/values-sl/strings_gamenames.xml b/common/src/main/res/values-sl/strings_gamenames.xml
new file mode 100644
index 000000000..e86193943
--- /dev/null
+++ b/common/src/main/res/values-sl/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Igra Snowball
+ Snežni spopad
+
diff --git a/common/src/main/res/values-sl/strings_invite.xml b/common/src/main/res/values-sl/strings_invite.xml
new file mode 100644
index 000000000..4030a6571
--- /dev/null
+++ b/common/src/main/res/values-sl/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Povabite svoje prijatelje, da spremljajo Božička z Googlom
+ Preskusite Spremljanje Božičkove poti in glejte, kako Božiček leta po svetu.
+ V Googlovi aplikaciji Spremljanje Božičkove poti sem v igri %2$s s palčki dosegel rezultat %1$d. Me lahko premagate?
+
diff --git a/common/src/main/res/values-sl/strings_jetpack.xml b/common/src/main/res/values-sl/strings_jetpack.xml
new file mode 100644
index 000000000..596140dbc
--- /dev/null
+++ b/common/src/main/res/values-sl/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Prijavite se, da odklenete dosežke in objavite svoj rezultat.
+ Igraj znova
+ Rezultat
+
diff --git a/common/src/main/res/values-sv/strings_gamenames.xml b/common/src/main/res/values-sv/strings_gamenames.xml
new file mode 100644
index 000000000..b84b3125b
--- /dev/null
+++ b/common/src/main/res/values-sv/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Tomtenisse-jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Spelet Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-sv/strings_invite.xml b/common/src/main/res/values-sv/strings_invite.xml
new file mode 100644
index 000000000..47ff8e79e
--- /dev/null
+++ b/common/src/main/res/values-sv/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Bjud in dina vänner att följa Jultomten med Google
+ Prova Följ jultomten och se hur tomten flyger runt hela världen!
+ Jag fick %1$d poäng när jag spelade %2$s med tomtenissarna i Googles Följ Jultomten. Kan du slå det?
+
diff --git a/common/src/main/res/values-sv/strings_jetpack.xml b/common/src/main/res/values-sv/strings_jetpack.xml
new file mode 100644
index 000000000..ad1cbe0e6
--- /dev/null
+++ b/common/src/main/res/values-sv/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Logga in för att låsa upp prestationer och skicka in ditt resultat.
+ Spela igen
+ Poäng
+
diff --git a/common/src/main/res/values-sw600dp/dimens.xml b/common/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000..67af92005
--- /dev/null
+++ b/common/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 20dp
+ 75dp
+
+
diff --git a/common/src/main/res/values-sw720dp/dimens.xml b/common/src/main/res/values-sw720dp/dimens.xml
new file mode 100644
index 000000000..70387207b
--- /dev/null
+++ b/common/src/main/res/values-sw720dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 28sp
+ 120dp
+
+
diff --git a/common/src/main/res/values-ta/strings_gamenames.xml b/common/src/main/res/values-ta/strings_gamenames.xml
new file mode 100644
index 000000000..0db312ac1
--- /dev/null
+++ b/common/src/main/res/values-ta/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ கம் பால்
+ மெமரி
+ எல்ஃப் ஜெட்பேக்
+ ஸ்னோகுளோப்
+ ராக்கெட் ஸ்லெய்
+ டேஷர் டான்சர்
+ ஸ்னோபால் கேம்
+ ஸ்னோடவுன்
+
diff --git a/common/src/main/res/values-ta/strings_invite.xml b/common/src/main/res/values-ta/strings_invite.xml
new file mode 100644
index 000000000..d5450cdff
--- /dev/null
+++ b/common/src/main/res/values-ta/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Google உடன் சேர்ந்து சான்டாவைக் கண்காணிக்க நண்பர்களை அழைக்கவும்
+ சான்டா ட்ராக்கரைப் பயன்படுத்தி, சான்டாவின் பயண வழிகளைப் பார்க்கவும்!
+ Google இன் சான்டா டிராக்கரில் குட்டித் தேவதைகளுடன் %2$s கேமில் %1$d ஸ்கோர் செய்துள்ளேன்! இதை முறியடிக்க முடியுமா?
+
diff --git a/common/src/main/res/values-ta/strings_jetpack.xml b/common/src/main/res/values-ta/strings_jetpack.xml
new file mode 100644
index 000000000..7a2a54f93
--- /dev/null
+++ b/common/src/main/res/values-ta/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ சாதனைகளை எட்டவும் உங்கள் ஸ்கோரை வெளியிடவும், உள்நுழையவும்!
+ மீண்டும் விளையாடு
+ ஸ்கோர்
+
diff --git a/common/src/main/res/values-th/strings_gamenames.xml b/common/src/main/res/values-th/strings_gamenames.xml
new file mode 100644
index 000000000..f6e5a3b68
--- /dev/null
+++ b/common/src/main/res/values-th/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ เกมความจำ
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values-th/strings_invite.xml b/common/src/main/res/values-th/strings_invite.xml
new file mode 100644
index 000000000..db165e988
--- /dev/null
+++ b/common/src/main/res/values-th/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ กำลังเชิญเพื่อนๆ ของคุณมาร่วมตามติดซานต้าบน Google
+ ลองตามติดซานต้าและดูซานต้าบินไปรอบโลก!
+ ฉันได้ %1$d คะแนนในการเล่นเกม %2$s กับชาวเอลฟ์ในตามติดซานต้าของ Google คุณเอาชนะฉันได้ไหม
+
diff --git a/common/src/main/res/values-th/strings_jetpack.xml b/common/src/main/res/values-th/strings_jetpack.xml
new file mode 100644
index 000000000..45a78ab07
--- /dev/null
+++ b/common/src/main/res/values-th/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ ลงชื่อเข้าใช้เพื่อปลดล็อกรางวัลพิเศษและโพสต์คะแนนของคุณ
+ เล่นอีกครั้ง
+ คะแนน
+
diff --git a/common/src/main/res/values-tl/strings_gamenames.xml b/common/src/main/res/values-tl/strings_gamenames.xml
new file mode 100644
index 000000000..53e834857
--- /dev/null
+++ b/common/src/main/res/values-tl/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Larong Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-tl/strings_invite.xml b/common/src/main/res/values-tl/strings_invite.xml
new file mode 100644
index 000000000..ad736646d
--- /dev/null
+++ b/common/src/main/res/values-tl/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Imbitahan ang iyong mga kaibigan na subaybayan si Santa sa Google
+ Subukan ang Santa Tracker at panoorin ang paglipad ni Santa sa buong mundo!
+ Nakapuntos ako ng %1$d sa paglalaro ng %2$s game na may elves sa Santa Tracker ng Google, kaya mo bang talunin iyon?
+
diff --git a/common/src/main/res/values-tl/strings_jetpack.xml b/common/src/main/res/values-tl/strings_jetpack.xml
new file mode 100644
index 000000000..0723cba66
--- /dev/null
+++ b/common/src/main/res/values-tl/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Mag-sign in upang mai-unlock ang mga achievement at mai-post ang iyong score!
+ Maglaro Muli
+ Score
+
diff --git a/common/src/main/res/values-uk/strings_gamenames.xml b/common/src/main/res/values-uk/strings_gamenames.xml
new file mode 100644
index 000000000..c74f9043d
--- /dev/null
+++ b/common/src/main/res/values-uk/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Гамбол
+ Згадати все
+ Реактивні ельфи
+ Снігова куля
+ Реактивні санчата
+ Олені
+ Гра \"Сніжки\"
+ Сніжки
+
diff --git a/common/src/main/res/values-uk/strings_invite.xml b/common/src/main/res/values-uk/strings_invite.xml
new file mode 100644
index 000000000..a51b7b640
--- /dev/null
+++ b/common/src/main/res/values-uk/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Запропонуйте друзям стежити за мандрівкою Діда Мороза в Google
+ Завантажте додаток Де Дід Мороз і стежте за пересуванням Діда Мороза по світу!
+ У грі \"%2$s\" у Google Де Дід Мороз у мене вже стільки очок: %1$d. Наберете більше?
+
diff --git a/common/src/main/res/values-uk/strings_jetpack.xml b/common/src/main/res/values-uk/strings_jetpack.xml
new file mode 100644
index 000000000..1d1b10ed0
--- /dev/null
+++ b/common/src/main/res/values-uk/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Увійдіть, щоб розблокувати досягнення й повідомити свій результат іншим.
+ Грати ще раз
+ Результат
+
diff --git a/common/src/main/res/values-vi/strings_gamenames.xml b/common/src/main/res/values-vi/strings_gamenames.xml
new file mode 100644
index 000000000..c379c4d70
--- /dev/null
+++ b/common/src/main/res/values-vi/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Trò chơi Gumball
+ Trò chơi Trí nhớ
+ Trò chơi Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Trò chơi Snowball
+ Snowdown
+
diff --git a/common/src/main/res/values-vi/strings_invite.xml b/common/src/main/res/values-vi/strings_invite.xml
new file mode 100644
index 000000000..753baa2af
--- /dev/null
+++ b/common/src/main/res/values-vi/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ Mời bạn bè theo dõi Ông già Noel với Google
+ Hãy dùng thử công cụ Theo chân ông già Noel và xem ông già Noel bay khắp thế giới!
+ Tôi đã ghi %1$d khi chơi trò %2$s với các chú lùn trong công cụ Theo chân ông già Noel của Google, bạn có thể đánh bại số điểm đó không?
+
diff --git a/common/src/main/res/values-vi/strings_jetpack.xml b/common/src/main/res/values-vi/strings_jetpack.xml
new file mode 100644
index 000000000..ac07d5c4b
--- /dev/null
+++ b/common/src/main/res/values-vi/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Đăng nhập để gặt hái thành tích và đăng điểm của bạn!
+ Chơi lại
+ Điểm
+
diff --git a/common/src/main/res/values-xhdpi/dimens.xml b/common/src/main/res/values-xhdpi/dimens.xml
new file mode 100644
index 000000000..6697d3c78
--- /dev/null
+++ b/common/src/main/res/values-xhdpi/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 12sp
+ 50dp
+
+
diff --git a/common/src/main/res/values-zh-rCN/strings_gamenames.xml b/common/src/main/res/values-zh-rCN/strings_gamenames.xml
new file mode 100644
index 000000000..66587b8fd
--- /dev/null
+++ b/common/src/main/res/values-zh-rCN/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball 游戏
+ Snowdown
+
diff --git a/common/src/main/res/values-zh-rCN/strings_invite.xml b/common/src/main/res/values-zh-rCN/strings_invite.xml
new file mode 100644
index 000000000..e4e256ba4
--- /dev/null
+++ b/common/src/main/res/values-zh-rCN/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ 邀请好友在 Google 上追踪圣诞老人
+ 欢迎体验“追踪圣诞老人”,跟随圣诞老人环游世界!
+ 我在 Google 的“追踪圣诞老人”中与小精灵们玩“%2$s”游戏时得了 %1$d 分。你敢来挑战吗?
+
diff --git a/common/src/main/res/values-zh-rCN/strings_jetpack.xml b/common/src/main/res/values-zh-rCN/strings_jetpack.xml
new file mode 100644
index 000000000..175392b87
--- /dev/null
+++ b/common/src/main/res/values-zh-rCN/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ 登录即可记录您的成就并发布您的得分!
+ 重玩
+ 得分
+
diff --git a/common/src/main/res/values-zh-rHK/strings_gamenames.xml b/common/src/main/res/values-zh-rHK/strings_gamenames.xml
new file mode 100644
index 000000000..1dff059dd
--- /dev/null
+++ b/common/src/main/res/values-zh-rHK/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ 小精靈飛行背包
+ 雪景球
+ 火箭雪橇
+ 猛衝‧跳舞
+ 雪球遊戲
+ Snowdown
+
diff --git a/common/src/main/res/values-zh-rHK/strings_invite.xml b/common/src/main/res/values-zh-rHK/strings_invite.xml
new file mode 100644
index 000000000..34e710996
--- /dev/null
+++ b/common/src/main/res/values-zh-rHK/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ 邀請好友在 Google 追蹤聖誕老人
+ 試玩「追蹤聖誕老人」,追看聖誕老人遨遊世界各地!
+ 我與小精靈在 Google「追蹤聖誕老人」玩%1$d時得獲得 %2$s 分,您能打敗我嗎?
+
diff --git a/common/src/main/res/values-zh-rHK/strings_jetpack.xml b/common/src/main/res/values-zh-rHK/strings_jetpack.xml
new file mode 100644
index 000000000..d7c3a81c1
--- /dev/null
+++ b/common/src/main/res/values-zh-rHK/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ 登入即可解鎖成就並發佈您的得分!
+ 再玩一次
+ 樂譜
+
diff --git a/common/src/main/res/values-zh-rTW/strings_gamenames.xml b/common/src/main/res/values-zh-rTW/strings_gamenames.xml
new file mode 100644
index 000000000..907448c97
--- /dev/null
+++ b/common/src/main/res/values-zh-rTW/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ 雪花玻璃球
+ 火箭雪橇
+ 馴鹿快跑
+ 雪花玻璃球遊戲
+ 雪球爭霸戰
+
diff --git a/common/src/main/res/values-zh-rTW/strings_invite.xml b/common/src/main/res/values-zh-rTW/strings_invite.xml
new file mode 100644
index 000000000..a90a9c4d4
--- /dev/null
+++ b/common/src/main/res/values-zh-rTW/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ 邀請好友與 Google 一起追蹤聖誕老人
+ 快來體驗聖誕老人追蹤器,和聖誕老人一起遨遊世界!
+ 我在 Google 聖誕老人追蹤器中與小精靈一起玩「%2$s」遊戲,獲得 %1$d 分!你能打敗我嗎?
+
diff --git a/common/src/main/res/values-zh-rTW/strings_jetpack.xml b/common/src/main/res/values-zh-rTW/strings_jetpack.xml
new file mode 100644
index 000000000..c0c67b7f2
--- /dev/null
+++ b/common/src/main/res/values-zh-rTW/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ 登入即可解開遊戲關卡並發表您的得分!
+ 再玩一次
+ 得分
+
diff --git a/common/src/main/res/values-zh/strings_gamenames.xml b/common/src/main/res/values-zh/strings_gamenames.xml
new file mode 100644
index 000000000..66587b8fd
--- /dev/null
+++ b/common/src/main/res/values-zh/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball 游戏
+ Snowdown
+
diff --git a/common/src/main/res/values-zh/strings_invite.xml b/common/src/main/res/values-zh/strings_invite.xml
new file mode 100644
index 000000000..e4e256ba4
--- /dev/null
+++ b/common/src/main/res/values-zh/strings_invite.xml
@@ -0,0 +1,6 @@
+
+
+ 邀请好友在 Google 上追踪圣诞老人
+ 欢迎体验“追踪圣诞老人”,跟随圣诞老人环游世界!
+ 我在 Google 的“追踪圣诞老人”中与小精灵们玩“%2$s”游戏时得了 %1$d 分。你敢来挑战吗?
+
diff --git a/common/src/main/res/values-zh/strings_jetpack.xml b/common/src/main/res/values-zh/strings_jetpack.xml
new file mode 100644
index 000000000..175392b87
--- /dev/null
+++ b/common/src/main/res/values-zh/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ 登录即可记录您的成就并发布您的得分!
+ 重玩
+ 得分
+
diff --git a/common/src/main/res/values/game_colors.xml b/common/src/main/res/values/game_colors.xml
new file mode 100644
index 000000000..a65a2f29e
--- /dev/null
+++ b/common/src/main/res/values/game_colors.xml
@@ -0,0 +1,6 @@
+
+
+ #99ffffff
+ #ff25af31
+ #FF17B223
+
\ No newline at end of file
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
new file mode 100644
index 000000000..185e6f2ad
--- /dev/null
+++ b/common/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Common
+
diff --git a/common/src/main/res/values/strings_gamenames.xml b/common/src/main/res/values/strings_gamenames.xml
new file mode 100644
index 000000000..6529032bf
--- /dev/null
+++ b/common/src/main/res/values/strings_gamenames.xml
@@ -0,0 +1,11 @@
+
+
+ Gumball
+ Memory
+ Elf Jetpack
+ Snowglobe
+ Rocket Sleigh
+ Dasher Dancer
+ Snowball game
+ Snowdown
+
diff --git a/common/src/main/res/values/strings_invite.xml b/common/src/main/res/values/strings_invite.xml
new file mode 100644
index 000000000..931856330
--- /dev/null
+++ b/common/src/main/res/values/strings_invite.xml
@@ -0,0 +1,12 @@
+
+
+
+ Invite your friends to track Santa with Google
+
+
+ Try Santa Tracker and watch Santa fly around the world!
+
+
+ I scored %1$d playing the %2$s game with the elves in Google\'s Santa Tracker, can you beat that?
+
+
diff --git a/common/src/main/res/values/strings_jetpack.xml b/common/src/main/res/values/strings_jetpack.xml
new file mode 100644
index 000000000..a77357826
--- /dev/null
+++ b/common/src/main/res/values/strings_jetpack.xml
@@ -0,0 +1,6 @@
+
+
+ Sign in to unlock achievements and post your score!
+ Play Again
+ Score
+
diff --git a/common/src/main/res/xml/config_analytics_tracker.xml b/common/src/main/res/xml/config_analytics_tracker.xml
new file mode 100644
index 000000000..5d667745c
--- /dev/null
+++ b/common/src/main/res/xml/config_analytics_tracker.xml
@@ -0,0 +1,13 @@
+
+
+ 300
+
+
+ false
+
+
+ true
+
+
+ UA-37048309-4
+
\ No newline at end of file
diff --git a/dasherdancer/.gitignore b/dasherdancer/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/dasherdancer/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/dasherdancer/build.gradle b/dasherdancer/build.gradle
new file mode 100644
index 000000000..5e15ac2a0
--- /dev/null
+++ b/dasherdancer/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 23
+ }
+}
+
+dependencies {
+ compile project(':common')
+ compile rootProject.ext.supportV4
+ compile rootProject.ext.seismic
+}
diff --git a/dasherdancer/proguard-rules.pro b/dasherdancer/proguard-rules.pro
new file mode 100644
index 000000000..67abb7535
--- /dev/null
+++ b/dasherdancer/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/rpetit/android_sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/dasherdancer/src/main/AndroidManifest.xml b/dasherdancer/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..e6f01795f
--- /dev/null
+++ b/dasherdancer/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Character.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Character.java
new file mode 100644
index 000000000..d0afaf157
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Character.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+/**
+ * Interface for characters. To create a character, implement this interface. The animationKey passed
+ * to this interface's methods is called with one of the ANIM_* static values defined in this interface.
+ * Generally implementing classes should use an array to store their durations, frame indices arrays,
+ * and frame arrays.
+ */
+public interface Character {
+
+ public static int ANIM_IDLE = 0;
+ public static int ANIM_TAP = 1;
+ public static int ANIM_SHAKE = 2;
+ public static int ANIM_SWIPE_DOWN = 3;
+ public static int ANIM_SWIPE_UP = 4;
+ public static int ANIM_SWIPE_LEFT = 5;
+ public static int ANIM_SWIPE_RIGHT = 6;
+ public static int ANIM_PINCH_IN = 7;
+ public static int ANIM_PINCH_OUT = 8;
+
+ long getDuration(int animationKey);
+
+ int[] getFrameIndices(int animationKey);
+
+ int[] getFrames(int animationKey);
+
+ // The initial release used getClass().getSimpleName(), which was ProGuarded out.
+ // These strings are the pro-guarded names from the released version. In future releases
+ // these should be changed to the names of the characters.
+ String getCharacterName();
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterActivity.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterActivity.java
new file mode 100644
index 000000000..446cbb886
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class CharacterActivity extends Activity {
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_character);
+
+ AnalyticsManager.initializeAnalyticsTracker(this);
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_dasher_charselect);
+ }
+
+ public void onCharacterClick(View view) {
+ int characterId = -1;
+ if (view.getId() == R.id.btn_character_santa) {
+ characterId = DasherDancerActivity.CHARACTER_ID_SANTA;
+ } else if (view.getId() == R.id.btn_character_elf) {
+ characterId = DasherDancerActivity.CHARACTER_ID_ELF;
+ } else if (view.getId() == R.id.btn_character_reindeer) {
+ characterId = DasherDancerActivity.CHARACTER_ID_REINDEER;
+ } else if (view.getId() == R.id.btn_character_snowman) {
+ characterId = DasherDancerActivity.CHARACTER_ID_SNOWMAN;
+ }
+ Intent result = new Intent();
+ result.putExtra(DasherDancerActivity.EXTRA_CHARACTER_ID, characterId);
+ setResult(Activity.RESULT_OK, result);
+ finish();
+ }
+
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterAdapter.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterAdapter.java
new file mode 100644
index 000000000..5be82df09
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/CharacterAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+
+import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+
+import java.lang.*;
+
+public class CharacterAdapter extends PagerAdapter {
+
+ private static final String LOG_TAG = CharacterAdapter.class.getSimpleName();
+
+ private static final int[] mBackgrounds = new int[]{
+ R.color.bg_blue,R.color.bg_pink,R.color.bg_purple,
+ R.color.bg_green,R.color.bg_red,R.color.bg_orange,
+ R.color.bg_yellow
+ };
+
+ private SparseArray mViews;
+ private Character[] mCharacters;
+
+ public CharacterAdapter(Character[] characters) {
+ mCharacters = characters;
+ mViews = new SparseArray();
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ if (position < mCharacters.length) {
+ View view = mViews.get(position);
+ if ((view == null) && (object != null)) {
+ try {
+ view = (ImageView)object;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, "Wrong type of object supplied to destroyItem.");
+ }
+ }
+
+ if (view != null) {
+ container.removeView(view);
+ }
+
+ mViews.remove(position);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if(mCharacters != null) {
+ return mCharacters.length;
+ }
+ return 0;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ ImageView view = null;
+
+ if (position < mCharacters.length) {
+ if (mViews.get(position) != null) {
+ view = mViews.get(position);
+ } else {
+ view = new FrameAnimationView(container.getContext());
+ view.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ //Load the first idle frame.
+ view.setBackgroundResource(mBackgrounds[position]);
+ view.setTag(position);
+ mViews.put(position, view);
+ }
+
+ LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ container.addView(view, lp);
+ }
+
+ return view;
+ }
+
+ @Override
+ public boolean isViewFromObject(View arg0, Object arg1) {
+ return arg0 == arg1;
+ }
+
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/DasherDancerActivity.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/DasherDancerActivity.java
new file mode 100644
index 000000000..b64a15d8d
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/DasherDancerActivity.java
@@ -0,0 +1,904 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.LruCache;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.google.android.apps.santatracker.games.PlayGamesFragment;
+import com.google.android.apps.santatracker.games.SignInListener;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.gms.games.Games;
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.squareup.seismic.ShakeDetector;
+import com.squareup.seismic.ShakeDetector.Listener;
+
+import java.util.HashSet;
+
+public class DasherDancerActivity extends FragmentActivity implements
+ OnGestureListener, OnScaleGestureListener, Handler.Callback, Listener, SensorEventListener,
+ AnimatorListener, OnPageChangeListener, SignInListener {
+
+ /**
+ * Extra key used to pass back the character id that should be selected, set by the CharacterActivity.
+ */
+ public static final String EXTRA_CHARACTER_ID = "extra_character_id";
+
+ //Character ids, which are also indices in the sCharacters array.
+ public static final int CHARACTER_ID_SANTA = 0;
+ public static final int CHARACTER_ID_ELF = 1;
+ public static final int CHARACTER_ID_REINDEER = 2;
+ public static final int CHARACTER_ID_SNOWMAN = 3;
+
+ /**
+ * Request code for calling CharacterActivity for result.
+ */
+ private static final int sCharacterRequestCode = 1;
+
+ /**
+ * Our array of playable characters. Add more characters here an create new CHARACTER_ID_* static variables.
+ */
+ private static final Character[] sCharacters = new Character[]{
+ new Santa(), new Elf(), new Reindeer(), new Snowman()
+ };
+
+ private boolean[] mCharacterInitialized = new boolean[] {
+ false, false, false, false
+ };
+
+ private int[][] mSoundIds = new int[][]{
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1}, //santa
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1}, //elf
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1}, //reindeer
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1} //snowman
+ };
+
+ private LruCache mMemoryCache;
+ private NoSwipeViewPager mPager;
+ private Handler mHandler;
+ private ShakeDetector mDetector;
+ private LoadBitmapsTask mLoadBitmapsTask;
+ private LoadAllBitmapsTask mLoadAllBitmapsTask;
+ private ObjectAnimator mAnimator;
+ private boolean mPlayingRest = false;
+ private boolean mAnimCanceled = false;
+ private boolean mAnimPlaying = false;
+ private boolean mScaling = false;
+ private boolean mInitialized = false;
+ private SoundPool mSoundPool;
+ private int mSoundId = -1;
+ private boolean mCanTouch = false;
+ private ObjectAnimator mProgressAnimator;
+ private ActivityManager mActivityManager;
+ private FirebaseAnalytics mMeasurement;
+
+ private PlayGamesFragment mGamesFragment;
+
+ // For achievements
+ private HashSet[] mAchievements;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_dasher_dancer);
+
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_dasher));
+
+ AnalyticsManager.initializeAnalyticsTracker(this);
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_dasher);
+
+ mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+
+ mMemoryCache = new LruCache(240) {
+ protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
+ if ((oldValue != null) && (oldValue != newValue)) {
+ oldValue.recycle();
+ oldValue = null;
+ }
+ }
+ };
+
+ CharacterAdapter adapter = new CharacterAdapter(sCharacters);
+ mPager = (NoSwipeViewPager) findViewById(R.id.character_pager);
+ mPager.setAdapter(adapter);
+ mPager.setGestureDetectorListeners(this, this, this);
+ mPager.setOnPageChangeListener(this);
+
+ mHandler = new Handler(getMainLooper(), this);
+
+ mDetector = new ShakeDetector(this);
+
+ mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
+ mSoundIds[0][Character.ANIM_PINCH_IN] = mSoundPool.load(this, R.raw.santa_pinchin, 1);
+ mSoundIds[0][Character.ANIM_PINCH_OUT] = mSoundPool.load(this, R.raw.santa_pinchout, 1);
+ mSoundIds[0][Character.ANIM_SHAKE] = mSoundPool.load(this, R.raw.santa_shake, 1);
+ mSoundIds[0][Character.ANIM_SWIPE_UP] = mSoundPool.load(this, R.raw.santa_swipeup, 1);
+ mSoundIds[0][Character.ANIM_SWIPE_LEFT] = mSoundPool.load(this, R.raw.santa_swipeleft, 1);
+ mSoundIds[0][Character.ANIM_SWIPE_RIGHT] = mSoundPool.load(this, R.raw.santa_swiperight, 1);
+ mSoundIds[0][Character.ANIM_SWIPE_DOWN] = mSoundPool.load(this, R.raw.santa_swipedown, 1);
+ mSoundIds[0][Character.ANIM_TAP] = mSoundPool.load(this, R.raw.santa_tap, 1);
+ mSoundIds[1][Character.ANIM_PINCH_IN] = mSoundPool.load(this, R.raw.elf_pinchin_ball, 1);
+ mSoundIds[1][Character.ANIM_PINCH_OUT] = mSoundPool.load(this, R.raw.elf_pinchout, 1);
+ mSoundIds[1][Character.ANIM_SHAKE] = mSoundPool.load(this, R.raw.elf_shake2, 1);
+ mSoundIds[1][Character.ANIM_SWIPE_DOWN] = mSoundPool.load(this, R.raw.elf_swipedown2, 1);
+ mSoundIds[1][Character.ANIM_SWIPE_UP] = mSoundPool.load(this, R.raw.elf_swipeup2, 1);
+ mSoundIds[1][Character.ANIM_SWIPE_LEFT] = mSoundPool.load(this, R.raw.elf_swipeleft, 1);
+ mSoundIds[1][Character.ANIM_SWIPE_RIGHT] = mSoundPool.load(this, R.raw.elf_swiperight, 1);
+ mSoundIds[1][Character.ANIM_TAP] = mSoundPool.load(this, R.raw.elf_tap3, 1);
+ mSoundIds[2][Character.ANIM_PINCH_IN] = mSoundPool.load(this, R.raw.reindeer_pinchin, 1);
+ mSoundIds[2][Character.ANIM_PINCH_OUT] = mSoundPool.load(this, R.raw.reindeer_pinchout, 1);
+ mSoundIds[2][Character.ANIM_SHAKE] = mSoundPool.load(this, R.raw.reindeer_shake, 1);
+ mSoundIds[2][Character.ANIM_SWIPE_UP] = mSoundPool.load(this, R.raw.reindeer_swipeup, 1);
+ mSoundIds[2][Character.ANIM_SWIPE_DOWN] = mSoundPool.load(this, R.raw.reindeer_swipedown, 1);
+ mSoundIds[2][Character.ANIM_SWIPE_LEFT] = mSoundPool.load(this, R.raw.reindeer_swipeleft, 1);
+ mSoundIds[2][Character.ANIM_SWIPE_RIGHT] = mSoundPool.load(this, R.raw.reindeer_swiperight, 1);
+ mSoundIds[2][Character.ANIM_TAP] = mSoundPool.load(this, R.raw.reindeer_tap2, 1);
+ mSoundIds[3][Character.ANIM_PINCH_IN] = mSoundPool.load(this, R.raw.snowman_pinchin, 1);
+ mSoundIds[3][Character.ANIM_PINCH_OUT] = mSoundPool.load(this, R.raw.snowman_pinchout, 1);
+ mSoundIds[3][Character.ANIM_SHAKE] = mSoundPool.load(this, R.raw.snowman_shake, 1);
+ mSoundIds[3][Character.ANIM_SWIPE_UP] = mSoundPool.load(this, R.raw.snowman_swipeup, 1);
+ mSoundIds[3][Character.ANIM_SWIPE_DOWN] = mSoundPool.load(this, R.raw.snowman_swipedown, 1);
+ mSoundIds[3][Character.ANIM_SWIPE_LEFT] = mSoundPool.load(this, R.raw.snowman_swipeleft, 1);
+ mSoundIds[3][Character.ANIM_SWIPE_RIGHT] = mSoundPool.load(this, R.raw.snowman_swiperight, 1);
+ mSoundIds[3][Character.ANIM_TAP] = mSoundPool.load(this, R.raw.snowman_tap, 1);
+
+ mAchievements = new HashSet[4];
+ mAchievements[0] = new HashSet();
+ mAchievements[1] = new HashSet();
+ mAchievements[2] = new HashSet();
+ mAchievements[3] = new HashSet();
+
+ mProgressAnimator = ObjectAnimator.ofFloat(findViewById(R.id.progress),"rotation",360f);
+ mProgressAnimator.setDuration(4000);
+ mProgressAnimator.start();
+
+ mGamesFragment = PlayGamesFragment.getInstance(this, this);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ ImmersiveModeHelper.setImmersiveSticky(getWindow());
+ ImmersiveModeHelper.installSystemUiVisibilityChangeListener(getWindow());
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ SensorManager manager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
+ Sensor accel = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ manager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL);
+ mDetector.start(manager);
+
+ if(mInitialized) {
+ //Start the animation for the first character.
+ mPager.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ loadAnimation(true,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_IDLE),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_IDLE),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_IDLE));
+ }
+
+ }, 300);
+ }
+ else {
+ if(mLoadAllBitmapsTask != null) {
+ mLoadAllBitmapsTask.cancel(true);
+ }
+ mLoadAllBitmapsTask = new LoadAllBitmapsTask();
+ mLoadAllBitmapsTask.execute(sCharacters[mPager.getCurrentItem()]);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ mDetector.stop();
+
+ SensorManager manager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
+ manager.unregisterListener(this);;
+
+ if(mAnimator != null) {
+ mAnimator.cancel();
+ }
+ FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
+ if (character != null) {
+ character.setImageDrawable(null);
+ }
+ }
+
+ /**
+ * Finishes the activity.
+ * @param view
+ */
+ public void onNavClick(View view) {
+ if(mLoadBitmapsTask != null) {
+ mLoadBitmapsTask.cancel(true);
+ }
+ if(mAnimator != null) {
+ mAnimator.cancel();
+ }
+ finish();
+ }
+
+ /**
+ * Starts the CharacterActivity for result. That result is an integer that corresponds to
+ * the index of an entry in sCharacters.
+ * @param view
+ */
+ public void onChangeClick(View view) {
+ if(mLoadBitmapsTask != null) {
+ mLoadBitmapsTask.cancel(true);
+ }
+ if(mLoadAllBitmapsTask != null) {
+ mLoadAllBitmapsTask.cancel(true);
+ }
+ if(mAnimator != null) {
+ mAnimator.cancel();
+ }
+ FrameAnimationView character = (FrameAnimationView) mPager.findViewWithTag(mPager.getCurrentItem());
+ character.setImageDrawable(null);
+ character.setFrames(null, null);
+ character.invalidate();
+ Intent intent = new Intent(this, CharacterActivity.class);
+ startActivityForResult(intent, sCharacterRequestCode);
+ }
+
+ /**
+ * Moves the view pager to the next character to the left of the current position.
+ */
+ public void onLeftClick(View view) {
+ final int currentPosition = mPager.getCurrentItem();
+ if(currentPosition != 0) {
+ characterSelectedHelper(currentPosition - 1, true);
+ }
+ }
+
+ /**
+ * Moves the view pager to the next character to the right of the current position.
+ */
+ public void onRightClick(View view) {
+ final int currentPosition = mPager.getCurrentItem();
+ if(currentPosition != mPager.getAdapter().getCount()-1) {
+ characterSelectedHelper(currentPosition + 1, true);
+ }
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ //Ignore this.
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_TAP];
+ }
+
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_tap));
+
+ updateGestureAchievements(Character.ANIM_TAP);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_TAP),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_TAP),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_TAP));
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ //Ignore
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ float xDelta = Math.abs(e1.getX() - e2.getX());
+ float yDelta = Math.abs(e1.getY() - e2.getY());
+ if(xDelta > yDelta) {
+ //Moving side to side.
+ if(e1.getX() > e2.getX()) {
+ //Moving left.
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_LEFT];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_swipe_left));
+ updateGestureAchievements(Character.ANIM_SWIPE_LEFT);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_LEFT),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_LEFT),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_LEFT));
+ }
+ else if(e2.getX() > e1.getX()) {
+ //Moving right.
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_RIGHT];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_swipe_right));
+ updateGestureAchievements(Character.ANIM_SWIPE_RIGHT);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_RIGHT),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_RIGHT),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_RIGHT));
+ }
+ }
+ else {
+ //We are moving up and down
+ if(e1.getY() > e2.getY()) {
+ //Moving up.
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_UP];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_swipe_up));
+ updateGestureAchievements(Character.ANIM_SWIPE_UP);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_UP),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_UP),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_UP));
+ }
+ else if(e2.getY() > e1.getY()) {
+ //Moving down.
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SWIPE_DOWN];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_swipe_down));
+ updateGestureAchievements(Character.ANIM_SWIPE_DOWN);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SWIPE_DOWN),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SWIPE_DOWN),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SWIPE_DOWN));
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ loadAnimation(true,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_IDLE),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_IDLE),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_IDLE));
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+
+ return false;
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ mScaling = true;
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mScaling = false;
+ if(detector.getScaleFactor() > 1) {
+ //Pinch in
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_PINCH_IN];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_pinch_in));
+ updateGestureAchievements(Character.ANIM_PINCH_IN);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_PINCH_IN),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_PINCH_IN),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_PINCH_IN));
+ }
+ else if(detector.getScaleFactor() < 1) {
+ //Pinch out
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_PINCH_OUT];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_pinch_out));
+ updateGestureAchievements(Character.ANIM_PINCH_OUT);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_PINCH_OUT),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_PINCH_OUT),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_PINCH_OUT));
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ //Ignore this.
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ //Ignore this.
+ }
+
+ @Override
+ public void hearShake() {
+ if(!mAnimPlaying) {
+ mSoundId = mSoundIds[mPager.getCurrentItem()][Character.ANIM_SHAKE];
+ }
+ AnalyticsManager.sendEvent(getString(R.string.analytics_category_interaction),
+ sCharacters[mPager.getCurrentItem()].getCharacterName(),
+ getString(R.string.analytics_action_shake));
+ updateGestureAchievements(Character.ANIM_SHAKE);
+ loadAnimation(false,
+ sCharacters[mPager.getCurrentItem()].getDuration(Character.ANIM_SHAKE),
+ sCharacters[mPager.getCurrentItem()].getFrameIndices(Character.ANIM_SHAKE),
+ sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_SHAKE));
+ }
+
+ /**
+ * Helper method to load and start animations. Takes care of canceling any ongoing animations,
+ * and will return without executing anything if mAnimPlaying is true or mScaling is true.
+ * @param playingRest
+ * @param animationTime
+ * @param frameIndices
+ * @param frameResourceIds
+ */
+ private void loadAnimation(boolean playingRest, long animationTime, int[] frameIndices, int[] frameResourceIds) {
+ if((!playingRest && (mAnimPlaying || mScaling)) || !mCanTouch) {
+ return;
+ }
+ if(playingRest) {
+ mAnimPlaying = false;
+ }
+ else {
+ mAnimPlaying = true;
+ }
+ mPlayingRest = playingRest;
+ if(mLoadBitmapsTask != null) {
+ mLoadBitmapsTask.cancel(true);
+ mAnimator.cancel();
+ }
+ LoadBitmapsTask task = new LoadBitmapsTask(animationTime, frameIndices, frameResourceIds);
+ task.execute();
+ }
+
+ @Override
+ public void onSignInFailed() {
+
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+
+ }
+
+ private class LoadAllBitmapsTask extends AsyncTask {
+
+ final BitmapFactory.Options mOptions = new BitmapFactory.Options();
+
+ @Override
+ protected Void doInBackground(Character... params) {
+ mCanTouch = false;
+ //See if we can free up any memory before we allocate some ourselves.
+ //Request garbage collection.
+ System.gc();
+ Character c = params[0];
+
+ mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
+ mOptions.inSampleSize = getResources().getInteger(R.integer.res);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (mActivityManager.isLowRamDevice()) {
+ mOptions.inSampleSize *= 2;
+ }
+ }
+
+ for (int resourceId : c.getFrames(Character.ANIM_IDLE)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_TAP)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_SHAKE)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_SWIPE_UP)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_SWIPE_DOWN)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_SWIPE_LEFT)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_SWIPE_RIGHT)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_PINCH_IN)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+ for (int resourceId : c.getFrames(Character.ANIM_PINCH_OUT)) {
+ if(isCancelled()) {
+ break;
+ }
+ loadBitmapHelper(resourceId);
+ }
+
+ return null;
+ }
+
+ private void loadBitmapHelper(int resourceId) {
+ if(mMemoryCache.get(resourceId) == null) {
+ mMemoryCache.put(resourceId, BitmapFactory.decodeResource(
+ DasherDancerActivity.this.getResources(),
+ resourceId,
+ mOptions));
+ if (isCancelled()) {
+ // Remove the BMP we just added
+ // The check and remove should be atomic so we synchronize
+ // (There could be an evict going on so make sure it's still there...
+ synchronized(mMemoryCache) {
+ if (mMemoryCache.get(resourceId) != null) {
+ mMemoryCache.remove(resourceId);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ if(isCancelled()) {
+ return;
+ }
+
+ findViewById(R.id.progress).setVisibility(View.GONE);
+
+ Bitmap[] frames = new Bitmap[sCharacters[mPager.getCurrentItem()].getFrames(Character.ANIM_IDLE).length];
+ for(int i=0; i {
+
+ private int[] mFrames;
+ private int[] mFrameIndices;
+ private long mDuration;
+
+ public LoadBitmapsTask(long duration, int[] frameIndices, int[] frames) {
+ mFrameIndices = frameIndices;
+ mDuration = duration;
+ mFrames = frames;
+ }
+
+ @Override
+ protected Bitmap[] doInBackground(Void... params) {
+ Bitmap[] bitmaps = new Bitmap[mFrames.length];
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ options.inSampleSize = getResources().getInteger(R.integer.res);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (mActivityManager.isLowRamDevice()) {
+ options.inSampleSize *= 2;
+ }
+ }
+
+ for(int i=0; i= 0 && mFrameIndex < mFrameIndices.length
+ && mFrames[mFrameIndices[mFrameIndex]] != null && !mFrames[mFrameIndices[mFrameIndex]].isRecycled()) {
+ invalidate();
+ }
+ }
+
+ public void onDraw(Canvas c) {
+ if(mFrames != null && mFrameIndex >= 0 && mFrameIndex < mFrameIndices.length) {
+ setImageBitmap(mFrames[mFrameIndices[mFrameIndex]]);
+ }
+ if(getDrawable() == null) {
+ super.onDraw(c);
+ return;
+ }
+ if(((BitmapDrawable)getDrawable()).getBitmap() == null
+ || ((BitmapDrawable)getDrawable()).getBitmap().isRecycled()) {
+ return;
+ }
+ super.onDraw(c);
+ }
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/NoSwipeViewPager.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/NoSwipeViewPager.java
new file mode 100644
index 000000000..3446d58da
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/NoSwipeViewPager.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+
+public class NoSwipeViewPager extends ViewPager {
+
+ private GestureDetector mGestureDetector;
+ private ScaleGestureDetector mScaleGestureDetector;
+
+ public NoSwipeViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ //Take all events, as we are going to animate the current view based on touch events.
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean retVal = false;
+ if(ev.getPointerCount() > 1) {
+ retVal = mScaleGestureDetector.onTouchEvent(ev);
+ }
+ if(!retVal && ev.getPointerCount() == 1) {
+ retVal = mGestureDetector.onTouchEvent(ev) || retVal;
+ }
+ return retVal;
+ }
+
+ public void setGestureDetectorListeners(Context context,
+ OnGestureListener listener, OnScaleGestureListener scaleListener) {
+ mGestureDetector = new GestureDetector(context, listener);
+ mScaleGestureDetector = new ScaleGestureDetector(context, scaleListener);
+ }
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Reindeer.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Reindeer.java
new file mode 100644
index 000000000..bcc951b75
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Reindeer.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+public class Reindeer implements Character {
+
+ private static final long[] sDurations = new long[]{
+ 1000, 1000, 1000, 1000, 1000,
+ 1000, 1000, 1000, 1000
+ };
+
+ private static final int[][] sFrames = new int[][]{
+ {
+ R.drawable.reindeer_tap0001}, //idle
+ {R.drawable.reindeer_tap0001, R.drawable.reindeer_tap0002, R.drawable.reindeer_tap0003, R.drawable.reindeer_tap0004,
+ R.drawable.reindeer_tap0005, R.drawable.reindeer_tap0006, R.drawable.reindeer_tap0007, R.drawable.reindeer_tap0008,
+ R.drawable.reindeer_tap0009, R.drawable.reindeer_tap0010, R.drawable.reindeer_tap0011, R.drawable.reindeer_tap0012,
+ R.drawable.reindeer_tap0013, R.drawable.reindeer_tap0014, R.drawable.reindeer_tap0015, R.drawable.reindeer_tap0016,
+ R.drawable.reindeer_tap0017, R.drawable.reindeer_tap0018, R.drawable.reindeer_tap0019, R.drawable.reindeer_tap0020,
+ R.drawable.reindeer_tap0021, R.drawable.reindeer_tap0022, R.drawable.reindeer_tap0023, R.drawable.reindeer_tap0024},//tap
+ {R.drawable.reindeer_shake0001, R.drawable.reindeer_shake0002, R.drawable.reindeer_shake0003, R.drawable.reindeer_shake0004,
+ R.drawable.reindeer_shake0005, R.drawable.reindeer_shake0006, R.drawable.reindeer_shake0007, R.drawable.reindeer_shake0008,
+ R.drawable.reindeer_shake0009, R.drawable.reindeer_shake0010, R.drawable.reindeer_shake0011, R.drawable.reindeer_shake0012,
+ R.drawable.reindeer_shake0013, R.drawable.reindeer_shake0014, R.drawable.reindeer_shake0015, R.drawable.reindeer_shake0016,
+ R.drawable.reindeer_shake0017, R.drawable.reindeer_shake0018, R.drawable.reindeer_shake0019, R.drawable.reindeer_shake0020,
+ R.drawable.reindeer_shake0021, R.drawable.reindeer_shake0022, R.drawable.reindeer_shake0023, R.drawable.reindeer_shake0024},//shake
+ {R.drawable.reindeer_swipedown0001,
+ R.drawable.reindeer_swipedown0002,
+ R.drawable.reindeer_swipedown0003,
+ R.drawable.reindeer_swipedown0004,
+ R.drawable.reindeer_swipedown0005,
+ R.drawable.reindeer_swipedown0006,
+ R.drawable.reindeer_swipedown0007,
+ R.drawable.reindeer_swipedown0008,
+ R.drawable.reindeer_swipedown0009,
+ R.drawable.reindeer_swipedown0010,
+ R.drawable.reindeer_swipedown0011,
+ R.drawable.reindeer_swipedown0012,
+ R.drawable.reindeer_swipedown0013,
+ R.drawable.reindeer_swipedown0014,
+ R.drawable.reindeer_swipedown0015,
+ R.drawable.reindeer_swipedown0016,
+ R.drawable.reindeer_swipedown0017,
+ R.drawable.reindeer_swipedown0018,
+ R.drawable.reindeer_swipedown0019,
+ R.drawable.reindeer_swipedown0020,
+ R.drawable.reindeer_swipedown0021,
+ R.drawable.reindeer_swipedown0022,
+ R.drawable.reindeer_swipedown0023,
+ R.drawable.reindeer_swipedown0024},//swipe down
+ {R.drawable.reindeer_swipeup0002,
+ R.drawable.reindeer_swipeup0003,
+ R.drawable.reindeer_swipeup0004,
+ R.drawable.reindeer_swipeup0005,
+ R.drawable.reindeer_swipeup0006,
+ R.drawable.reindeer_swipeup0007,
+ R.drawable.reindeer_swipeup0008,
+ R.drawable.reindeer_swipeup0009,
+ R.drawable.reindeer_swipeup0010,
+ R.drawable.reindeer_swipeup0011,
+ R.drawable.reindeer_swipeup0012,
+ R.drawable.reindeer_swipeup0013,
+ R.drawable.reindeer_swipeup0014,
+ R.drawable.reindeer_swipeup0015,
+ R.drawable.reindeer_swipeup0016,
+ R.drawable.reindeer_swipeup0017,
+ R.drawable.reindeer_swipeup0018,
+ R.drawable.reindeer_swipeup0019,
+ R.drawable.reindeer_swipeup0020,
+ R.drawable.reindeer_swipeup0021,
+ R.drawable.reindeer_swipeup0022,
+ R.drawable.reindeer_swipeup0023,
+ R.drawable.reindeer_swipeup0024},//swipe up
+ {R.drawable.reindeer_swipeleft0001,
+ R.drawable.reindeer_swipeleft0002,
+ R.drawable.reindeer_swipeleft0003,
+ R.drawable.reindeer_swipeleft0004,
+ R.drawable.reindeer_swipeleft0005,
+ R.drawable.reindeer_swipeleft0006,
+ R.drawable.reindeer_swipeleft0007,
+ R.drawable.reindeer_swipeleft0008,
+ R.drawable.reindeer_swipeleft0009,
+ R.drawable.reindeer_swipeleft0010,
+ R.drawable.reindeer_swipeleft0011,
+ R.drawable.reindeer_swipeleft0012,
+ R.drawable.reindeer_swipeleft0013,
+ R.drawable.reindeer_swipeleft0014,
+ R.drawable.reindeer_swipeleft0015,
+ R.drawable.reindeer_swipeleft0016,
+ R.drawable.reindeer_swipeleft0017,
+ R.drawable.reindeer_swipeleft0018,
+ R.drawable.reindeer_swipeleft0019,
+ R.drawable.reindeer_swipeleft0020,
+ R.drawable.reindeer_swipeleft0021,
+ R.drawable.reindeer_swipeleft0022,
+ R.drawable.reindeer_swipeleft0023,
+ R.drawable.reindeer_swipeleft0024},//swipe left
+ {R.drawable.reindeer_swiperight0002,
+ R.drawable.reindeer_swiperight0003,
+ R.drawable.reindeer_swiperight0004,
+ R.drawable.reindeer_swiperight0005,
+ R.drawable.reindeer_swiperight0006,
+ R.drawable.reindeer_swiperight0007,
+ R.drawable.reindeer_swiperight0008,
+ R.drawable.reindeer_swiperight0009,
+ R.drawable.reindeer_swiperight0010,
+ R.drawable.reindeer_swiperight0011,
+ R.drawable.reindeer_swiperight0012,
+ R.drawable.reindeer_swiperight0013,
+ R.drawable.reindeer_swiperight0014,
+ R.drawable.reindeer_swiperight0015,
+ R.drawable.reindeer_swiperight0016,
+ R.drawable.reindeer_swiperight0017,
+ R.drawable.reindeer_swiperight0018,
+ R.drawable.reindeer_swiperight0019,
+ R.drawable.reindeer_swiperight0020,
+ R.drawable.reindeer_swiperight0021,
+ R.drawable.reindeer_swiperight0022,
+ R.drawable.reindeer_swiperight0023,
+ R.drawable.reindeer_swiperight0024},//swipe right
+ {R.drawable.reindeer_pinchout0001,
+ R.drawable.reindeer_pinchout0002,
+ R.drawable.reindeer_pinchout0003,
+ R.drawable.reindeer_pinchout0004,
+ R.drawable.reindeer_pinchout0005,
+ R.drawable.reindeer_pinchout0006,
+ R.drawable.reindeer_pinchout0007,
+ R.drawable.reindeer_pinchout0008,
+ R.drawable.reindeer_pinchout0009,
+ R.drawable.reindeer_pinchout0010,
+ R.drawable.reindeer_pinchout0011,
+ R.drawable.reindeer_pinchout0012,
+ R.drawable.reindeer_pinchout0013,
+ R.drawable.reindeer_pinchout0014,
+ R.drawable.reindeer_pinchout0015,
+ R.drawable.reindeer_pinchout0016,
+ R.drawable.reindeer_pinchout0017,
+ R.drawable.reindeer_pinchout0018,
+ R.drawable.reindeer_pinchout0019,
+ R.drawable.reindeer_pinchout0020,
+ R.drawable.reindeer_pinchout0021,
+ R.drawable.reindeer_pinchout0022,
+ R.drawable.reindeer_pinchout0023,
+ R.drawable.reindeer_pinchout0024},//pinch in
+ {R.drawable.reindeer_pinchin0001,
+ R.drawable.reindeer_pinchin0002,
+ R.drawable.reindeer_pinchin0003,
+ R.drawable.reindeer_pinchin0004,
+ R.drawable.reindeer_pinchin0005,
+ R.drawable.reindeer_pinchin0006,
+ R.drawable.reindeer_pinchin0007,
+ R.drawable.reindeer_pinchin0008,
+ R.drawable.reindeer_pinchin0009,
+ R.drawable.reindeer_pinchin0010,
+ R.drawable.reindeer_pinchin0011,
+ R.drawable.reindeer_pinchin0012,
+ R.drawable.reindeer_pinchin0013,
+ R.drawable.reindeer_pinchin0014,
+ R.drawable.reindeer_pinchin0015,
+ R.drawable.reindeer_pinchin0016,
+ R.drawable.reindeer_pinchin0017,
+ R.drawable.reindeer_pinchin0018,
+ R.drawable.reindeer_pinchin0019,
+ R.drawable.reindeer_pinchin0020,
+ R.drawable.reindeer_pinchin0021,
+ R.drawable.reindeer_pinchin0022,
+ R.drawable.reindeer_pinchin0023,
+ R.drawable.reindeer_pinchin0024}//pinch in
+ };
+
+ private static final int[][] sFrameIndices = new int[][]{
+ {0},//idle
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//tap
+ {0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23},//shake
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//swipe down
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22},//swipe up
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//swipe left
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22},//swipe right
+ {0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23},//pinch in
+ {0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23}//pinch out
+ };
+
+ @Override
+ public long getDuration(int animationKey) {
+ return sDurations[animationKey];
+ }
+
+ @Override
+ public int[] getFrameIndices(int animationKey) {
+ return sFrameIndices[animationKey];
+ }
+
+ @Override
+ public int[] getFrames(int animationKey) {
+ return sFrames[animationKey];
+ }
+
+ @Override
+ public String getCharacterName() {
+ return "r";
+ }
+
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Santa.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Santa.java
new file mode 100644
index 000000000..3f920a538
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Santa.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+import java.lang.*;
+
+public class Santa implements Character {
+
+ private static final long[] sDurations = new long[]{
+ 2400, 1000, 1000, 1000, 1000,
+ 1000, 1000, 1000, 1000
+ };
+
+ private static final int[][] sFrames = new int[][]{
+ {R.drawable.santa_idle0001,
+ R.drawable.santa_idle0002, R.drawable.santa_idle0003,
+ R.drawable.santa_idle0004, R.drawable.santa_idle0005,
+ R.drawable.santa_idle0006, R.drawable.santa_idle0007,
+ R.drawable.santa_idle0008,
+ R.drawable.santa_idle0009,
+ R.drawable.santa_idle0010, // index is 9
+ R.drawable.santa_idle0035, R.drawable.santa_idle0036,
+ R.drawable.santa_idle0037, R.drawable.santa_idle0038},//idle
+ {R.drawable.santa_tap0001, R.drawable.santa_tap0002, R.drawable.santa_tap0003,
+ R.drawable.santa_tap0004, R.drawable.santa_tap0005, R.drawable.santa_tap0006,
+ R.drawable.santa_tap0007, R.drawable.santa_tap0008, R.drawable.santa_tap0009,
+ R.drawable.santa_tap0010, R.drawable.santa_tap0011, R.drawable.santa_tap0012,
+ R.drawable.santa_tap0013, R.drawable.santa_tap0014, R.drawable.santa_tap0015,
+ R.drawable.santa_tap0016, R.drawable.santa_tap0017, R.drawable.santa_tap0018,
+ R.drawable.santa_tap0019, R.drawable.santa_tap0020, R.drawable.santa_tap0021,
+ R.drawable.santa_tap0022, R.drawable.santa_tap0023, R.drawable.santa_tap0024},//tap
+ {R.drawable.santa_shake0001,
+ R.drawable.santa_shake0002,
+ R.drawable.santa_shake0003,
+ R.drawable.santa_shake0004,
+ R.drawable.santa_shake0005,
+ R.drawable.santa_shake0006,
+ R.drawable.santa_shake0007,
+ R.drawable.santa_shake0008,
+ R.drawable.santa_shake0009,
+ R.drawable.santa_shake0010,
+ R.drawable.santa_shake0011,
+ R.drawable.santa_shake0012,
+ R.drawable.santa_shake0013,
+ R.drawable.santa_shake0014,
+ R.drawable.santa_shake0015,
+ R.drawable.santa_shake0016,
+ R.drawable.santa_shake0017,
+ R.drawable.santa_shake0018,
+ R.drawable.santa_shake0019,
+ R.drawable.santa_shake0020,
+ R.drawable.santa_shake0021,
+ R.drawable.santa_shake0022,
+ R.drawable.santa_shake0023,
+ R.drawable.santa_shake0024,
+ R.drawable.santa_idle0001},//shake
+ {R.drawable.santa_swipedown0001,
+ R.drawable.santa_swipedown0002,
+ R.drawable.santa_swipedown0003,
+ R.drawable.santa_swipedown0004,
+ R.drawable.santa_swipedown0005,
+ R.drawable.santa_swipedown0006,
+ R.drawable.santa_swipedown0007,
+ R.drawable.santa_swipedown0008,
+ R.drawable.santa_swipedown0009,
+ R.drawable.santa_swipedown0010,
+ R.drawable.santa_swipedown0011,
+ R.drawable.santa_swipedown0012,
+ R.drawable.santa_swipedown0013,
+ R.drawable.santa_swipedown0014,
+ R.drawable.santa_swipedown0015,
+ R.drawable.santa_swipedown0016,
+ R.drawable.santa_swipedown0017,
+ R.drawable.santa_swipedown0018,
+ R.drawable.santa_swipedown0019,
+ R.drawable.santa_swipedown0020,
+ R.drawable.santa_swipedown0021,
+ R.drawable.santa_swipedown0022,
+ R.drawable.santa_swipedown0023,
+ R.drawable.santa_swipedown0024,
+ R.drawable.santa_idle0001},//swipe down
+ {R.drawable.santa_swipeup0002,
+ R.drawable.santa_swipeup0003,
+ R.drawable.santa_swipeup0004,
+ R.drawable.santa_swipeup0005,
+ R.drawable.santa_swipeup0006,
+ R.drawable.santa_swipeup0007,
+ R.drawable.santa_swipeup0008,
+ R.drawable.santa_swipeup0009,
+ R.drawable.santa_swipeup0010,
+ R.drawable.santa_swipeup0011,
+ R.drawable.santa_swipeup0012,
+ R.drawable.santa_swipeup0013,
+ R.drawable.santa_swipeup0014,
+ R.drawable.santa_swipeup0015,
+ R.drawable.santa_swipeup0016,
+ R.drawable.santa_swipeup0017,
+ R.drawable.santa_swipeup0018,
+ R.drawable.santa_swipeup0019,
+ R.drawable.santa_swipeup0020,
+ R.drawable.santa_swipeup0021,
+ R.drawable.santa_swipeup0022,
+ R.drawable.santa_swipeup0023,
+ R.drawable.santa_swipeup0024,
+ R.drawable.santa_idle0001},//swipe up
+ {R.drawable.santa_swipeleft0001,
+ R.drawable.santa_swipeleft0002,
+ R.drawable.santa_swipeleft0003,
+ R.drawable.santa_swipeleft0004,
+ R.drawable.santa_swipeleft0005,
+ R.drawable.santa_swipeleft0006,
+ R.drawable.santa_swipeleft0007,
+ R.drawable.santa_swipeleft0008,
+ R.drawable.santa_swipeleft0009,
+ R.drawable.santa_swipeleft0010,
+ R.drawable.santa_swipeleft0011,
+ R.drawable.santa_swipeleft0012,
+ R.drawable.santa_swipeleft0013,
+ R.drawable.santa_swipeleft0014,
+ R.drawable.santa_swipeleft0015,
+ R.drawable.santa_swipeleft0016,
+ R.drawable.santa_swipeleft0017,
+ R.drawable.santa_swipeleft0018,
+ R.drawable.santa_swipeleft0019,
+ R.drawable.santa_swipeleft0020,
+ R.drawable.santa_swipeleft0021,
+ R.drawable.santa_swipeleft0022,
+ R.drawable.santa_swipeleft0023,
+ R.drawable.santa_swipeleft0024,
+ R.drawable.santa_idle0001},//swipe left
+ {R.drawable.santa_swipe_right20002,
+ R.drawable.santa_swipe_right20003,
+ R.drawable.santa_swipe_right20004,
+ R.drawable.santa_swipe_right20005,
+ R.drawable.santa_swipe_right20006,
+ R.drawable.santa_swipe_right20007,
+ R.drawable.santa_swipe_right20008,
+ R.drawable.santa_swipe_right20009,
+ R.drawable.santa_swipe_right20010,
+ R.drawable.santa_swipe_right20011,
+ R.drawable.santa_swipe_right20012,
+ R.drawable.santa_swipe_right20013,
+ R.drawable.santa_swipe_right20014,
+ R.drawable.santa_swipe_right20015,
+ R.drawable.santa_swipe_right20016,
+ R.drawable.santa_swipe_right20017,
+ R.drawable.santa_swipe_right20018,
+ R.drawable.santa_swipe_right20019,
+ R.drawable.santa_swipe_right20020,
+ R.drawable.santa_swipe_right20021,
+ R.drawable.santa_swipe_right20022,
+ R.drawable.santa_swipe_right20023,
+ R.drawable.santa_swipe_right20024,
+ R.drawable.santa_idle0001},//swipe right
+ {R.drawable.santa_pinchout20001,
+ R.drawable.santa_pinchout20002,
+ R.drawable.santa_pinchout20003,
+ R.drawable.santa_pinchout20004,
+ R.drawable.santa_pinchout20005,
+ R.drawable.santa_pinchout20006,
+ R.drawable.santa_pinchout20007,
+ R.drawable.santa_pinchout20008,
+ R.drawable.santa_pinchout20009,
+ R.drawable.santa_pinchout20010,
+ R.drawable.santa_pinchout20011,
+ R.drawable.santa_pinchout20012,
+ R.drawable.santa_pinchout20013,
+ R.drawable.santa_pinchout20014,
+ R.drawable.santa_pinchout20015,
+ R.drawable.santa_pinchout20016,
+ R.drawable.santa_pinchout20017,
+ R.drawable.santa_pinchout20018,
+ R.drawable.santa_pinchout20019,
+ R.drawable.santa_pinchout20020,
+ R.drawable.santa_pinchout20021,
+ R.drawable.santa_pinchout20022,
+ R.drawable.santa_pinchout20023,
+ R.drawable.santa_pinchout20024,
+ R.drawable.santa_idle0001},//pinch in
+ {R.drawable.santa_pinchin0001,
+ R.drawable.santa_pinchin0002,
+ R.drawable.santa_pinchin0003,
+ R.drawable.santa_pinchin0004,
+ R.drawable.santa_pinchin0005,
+ R.drawable.santa_pinchin0006,
+ R.drawable.santa_pinchin0007,
+ R.drawable.santa_pinchin0008,
+ R.drawable.santa_pinchin0009,
+ R.drawable.santa_pinchin0010,
+ R.drawable.santa_pinchin0011,
+ R.drawable.santa_pinchin0012,
+ R.drawable.santa_pinchin0013,
+ R.drawable.santa_pinchin0014,
+ R.drawable.santa_pinchin0015,
+ R.drawable.santa_pinchin0016,
+ R.drawable.santa_pinchin0017,
+ R.drawable.santa_pinchin0018,
+ R.drawable.santa_pinchin0019,
+ R.drawable.santa_pinchin0020,
+ R.drawable.santa_pinchin0021,
+ R.drawable.santa_pinchin0022,
+ R.drawable.santa_pinchin0023,
+ R.drawable.santa_pinchin0024,
+ R.drawable.santa_idle0001}//pinch out
+ };
+
+ private static final int[][] sFrameIndices = new int[][]{
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 8, 7, 6, 5, 4,
+ 3, 2, 1, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10,
+ 11, 12, 13, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0},//idle
+ {0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23},//tap
+ {0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24},//shake
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23, 24},//swipe down
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22, 23},//swipe up
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23,24},//swipe left
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//swipe right
+ {0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24},//pinch in
+ {0, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24}//pinch out
+ };
+
+ @Override
+ public long getDuration(int animationKey) {
+ return sDurations[animationKey];
+ }
+
+ @Override
+ public int[] getFrameIndices(int animationKey) {
+ return sFrameIndices[animationKey];
+ }
+
+ @Override
+ public int[] getFrames(int animationKey) {
+ return sFrames[animationKey];
+ }
+
+ @Override
+ public String getCharacterName() {
+ return "s";
+ }
+
+}
diff --git a/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Snowman.java b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Snowman.java
new file mode 100644
index 000000000..b80e793c1
--- /dev/null
+++ b/dasherdancer/src/main/java/com/google/android/apps/santatracker/dasherdancer/Snowman.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 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.dasherdancer;
+
+public class Snowman implements Character {
+
+ private static final long[] sDurations = new long[]{
+ 1000, 1000, 1000, 960, 1000,
+ 1000, 1000, 1000, 1000
+ };
+
+ private static final int[][] sFrames = new int[][]{
+ {
+ R.drawable.snowman_idle0001, R.drawable.snowman_idle0002, R.drawable.snowman_idle0003, R.drawable.snowman_idle0004,
+ R.drawable.snowman_idle0005, R.drawable.snowman_idle0006, R.drawable.snowman_idle0007, R.drawable.snowman_idle0008,
+ R.drawable.snowman_idle0009, R.drawable.snowman_idle0010, R.drawable.snowman_idle0011, R.drawable.snowman_idle0012,
+ R.drawable.snowman_idle0013, R.drawable.snowman_idle0014, R.drawable.snowman_idle0015, R.drawable.snowman_idle0016,
+ R.drawable.snowman_idle0017, R.drawable.snowman_idle0018, R.drawable.snowman_idle0019, R.drawable.snowman_idle0020,
+ R.drawable.snowman_idle0021, R.drawable.snowman_idle0022, R.drawable.snowman_idle0023, R.drawable.snowman_idle0024}, //idle
+ {R.drawable.snowman_tap0001, R.drawable.snowman_tap0002, R.drawable.snowman_tap0003, R.drawable.snowman_tap0004,
+ R.drawable.snowman_tap0005, R.drawable.snowman_tap0006, R.drawable.snowman_tap0007, R.drawable.snowman_tap0008,
+ R.drawable.snowman_tap0009, R.drawable.snowman_tap0010, R.drawable.snowman_tap0011, R.drawable.snowman_tap0012,
+ R.drawable.snowman_tap0013, R.drawable.snowman_tap0014, R.drawable.snowman_tap0015, R.drawable.snowman_tap0016,
+ R.drawable.snowman_tap0017, R.drawable.snowman_tap0018, R.drawable.snowman_tap0019, R.drawable.snowman_tap0020,
+ R.drawable.snowman_tap0021, R.drawable.snowman_tap0022, R.drawable.snowman_tap0023, R.drawable.snowman_tap0024},//tap
+ {R.drawable.snowman_shake0001, R.drawable.snowman_shake0002, R.drawable.snowman_shake0003, R.drawable.snowman_shake0004,
+ R.drawable.snowman_shake0005, R.drawable.snowman_shake0006, R.drawable.snowman_shake0007, R.drawable.snowman_shake0008,
+ R.drawable.snowman_shake0009, R.drawable.snowman_shake0010, R.drawable.snowman_shake0011, R.drawable.snowman_shake0012,
+ R.drawable.snowman_shake0013, R.drawable.snowman_shake0014, R.drawable.snowman_shake0015, R.drawable.snowman_shake0016,
+ R.drawable.snowman_shake0017, R.drawable.snowman_shake0018, R.drawable.snowman_shake0019, R.drawable.snowman_shake0020,
+ R.drawable.snowman_shake0021, R.drawable.snowman_shake0022, R.drawable.snowman_shake0023, R.drawable.snowman_shake0024},//shake
+ {R.drawable.snowman_swipedown0001,
+ R.drawable.snowman_swipedown0002,
+ R.drawable.snowman_swipedown0003,
+ R.drawable.snowman_swipedown0004,
+ R.drawable.snowman_swipedown0005,
+ R.drawable.snowman_swipedown0006,
+ R.drawable.snowman_swipedown0007,
+ R.drawable.snowman_swipedown0008,
+ R.drawable.snowman_swipedown0009,
+ R.drawable.snowman_swipedown0010,
+ R.drawable.snowman_swipedown0011,
+ R.drawable.snowman_swipedown0012,
+ R.drawable.snowman_swipedown0013,
+ R.drawable.snowman_swipedown0014,
+ R.drawable.snowman_swipedown0015,
+ R.drawable.snowman_swipedown0016,
+ R.drawable.snowman_swipedown0017,
+ R.drawable.snowman_swipedown0018,
+ R.drawable.snowman_swipedown0019,
+ R.drawable.snowman_swipedown0020,
+ R.drawable.snowman_swipedown0021,
+ R.drawable.snowman_swipedown0022,
+ R.drawable.snowman_swipedown0023},//swipe down
+ {R.drawable.snowman_swipeup0002,
+ R.drawable.snowman_swipeup0003,
+ R.drawable.snowman_swipeup0004,
+ R.drawable.snowman_swipeup0005,
+ R.drawable.snowman_swipeup0006,
+ R.drawable.snowman_swipeup0007,
+ R.drawable.snowman_swipeup0008,
+ R.drawable.snowman_swipeup0009,
+ R.drawable.snowman_swipeup0010,
+ R.drawable.snowman_swipeup0011,
+ R.drawable.snowman_swipeup0012,
+ R.drawable.snowman_swipeup0013,
+ R.drawable.snowman_swipeup0014,
+ R.drawable.snowman_swipeup0015,
+ R.drawable.snowman_swipeup0016,
+ R.drawable.snowman_swipeup0017,
+ R.drawable.snowman_swipeup0018,
+ R.drawable.snowman_swipeup0019,
+ R.drawable.snowman_swipeup0020,
+ R.drawable.snowman_swipeup0021,
+ R.drawable.snowman_swipeup0022,
+ R.drawable.snowman_swipeup0023,
+ R.drawable.snowman_swipeup0024},//swipe up
+ {R.drawable.snowman_swipeleft0001,
+ R.drawable.snowman_swipeleft0002,
+ R.drawable.snowman_swipeleft0003,
+ R.drawable.snowman_swipeleft0004,
+ R.drawable.snowman_swipeleft0005,
+ R.drawable.snowman_swipeleft0006,
+ R.drawable.snowman_swipeleft0007,
+ R.drawable.snowman_swipeleft0008,
+ R.drawable.snowman_swipeleft0009,
+ R.drawable.snowman_swipeleft0010,
+ R.drawable.snowman_swipeleft0011,
+ R.drawable.snowman_swipeleft0012,
+ R.drawable.snowman_swipeleft0013,
+ R.drawable.snowman_swipeleft0014,
+ R.drawable.snowman_swipeleft0015,
+ R.drawable.snowman_swipeleft0016,
+ R.drawable.snowman_swipeleft0017,
+ R.drawable.snowman_swipeleft0018,
+ R.drawable.snowman_swipeleft0019,
+ R.drawable.snowman_swipeleft0020,
+ R.drawable.snowman_swipeleft0021,
+ R.drawable.snowman_swipeleft0022,
+ R.drawable.snowman_swipeleft0023,
+ R.drawable.snowman_swipeleft0024},//swipe left
+ {R.drawable.snowman_swiperight0002,
+ R.drawable.snowman_swiperight0003,
+ R.drawable.snowman_swiperight0004,
+ R.drawable.snowman_swiperight0005,
+ R.drawable.snowman_swiperight0006,
+ R.drawable.snowman_swiperight0007,
+ R.drawable.snowman_swiperight0008,
+ R.drawable.snowman_swiperight0009,
+ R.drawable.snowman_swiperight0010,
+ R.drawable.snowman_swiperight0011,
+ R.drawable.snowman_swiperight0012,
+ R.drawable.snowman_swiperight0013,
+ R.drawable.snowman_swiperight0014,
+ R.drawable.snowman_swiperight0015,
+ R.drawable.snowman_swiperight0016,
+ R.drawable.snowman_swiperight0017,
+ R.drawable.snowman_swiperight0018,
+ R.drawable.snowman_swiperight0019,
+ R.drawable.snowman_swiperight0020,
+ R.drawable.snowman_swiperight0021,
+ R.drawable.snowman_swiperight0022,
+ R.drawable.snowman_swiperight0023,
+ R.drawable.snowman_swiperight0024},//swipe right
+ {R.drawable.snowman_pinchin0001, R.drawable.snowman_pinchin0002, R.drawable.snowman_pinchin0003, R.drawable.snowman_pinchin0004,
+ R.drawable.snowman_pinchin0005, R.drawable.snowman_pinchin0006, R.drawable.snowman_pinchin0007, R.drawable.snowman_pinchin0008,
+ R.drawable.snowman_pinchin0009, R.drawable.snowman_pinchin0010, R.drawable.snowman_pinchin0011, R.drawable.snowman_pinchin0012,
+ R.drawable.snowman_pinchin0013, R.drawable.snowman_pinchin0014, R.drawable.snowman_pinchin0015, R.drawable.snowman_pinchin0016,
+ R.drawable.snowman_pinchin0017, R.drawable.snowman_pinchin0018, R.drawable.snowman_pinchin0019, R.drawable.snowman_pinchin0020,
+ R.drawable.snowman_pinchin0021, R.drawable.snowman_pinchin0022, R.drawable.snowman_pinchin0023, R.drawable.snowman_pinchin0024},//pinch out
+ {R.drawable.snowman_pinchout0001,
+ R.drawable.snowman_pinchout0002,
+ R.drawable.snowman_pinchout0003,
+ R.drawable.snowman_pinchout0004,
+ R.drawable.snowman_pinchout0005,
+ R.drawable.snowman_pinchout0006,
+ R.drawable.snowman_pinchout0007,
+ R.drawable.snowman_pinchout0008,
+ R.drawable.snowman_pinchout0009,
+ R.drawable.snowman_pinchout0010,
+ R.drawable.snowman_pinchout0011,
+ R.drawable.snowman_pinchout0012,
+ R.drawable.snowman_pinchout0013,
+ R.drawable.snowman_pinchout0014,
+ R.drawable.snowman_pinchout0015,
+ R.drawable.snowman_pinchout0016,
+ R.drawable.snowman_pinchout0017,
+ R.drawable.snowman_pinchout0018,
+ R.drawable.snowman_pinchout0019,
+ R.drawable.snowman_pinchout0020,
+ R.drawable.snowman_pinchout0021,
+ R.drawable.snowman_pinchout0022,
+ R.drawable.snowman_pinchout0023,
+ R.drawable.snowman_pinchout0024}//pinch in
+ };
+
+ private static final int[][] sFrameIndices = new int[][]{
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//idle
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//tap
+ {0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23},//shake
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22},//swipe down
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22},//swipe up
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22,23},//swipe left
+ {0,1,2,3,4,5,6,7,
+ 8,9,10,11,12,13,14,15,
+ 16,17,18,19,20,21,22},//swipe right
+ {0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23},//pinch in
+ {0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23}//pinch out
+ };
+
+ @Override
+ public long getDuration(int animationKey) {
+ return sDurations[animationKey];
+ }
+
+ @Override
+ public int[] getFrameIndices(int animationKey) {
+ return sFrameIndices[animationKey];
+ }
+
+ @Override
+ public int[] getFrames(int animationKey) {
+ return sFrames[animationKey];
+ }
+
+ @Override
+ public String getCharacterName() {
+ return "t";
+ }
+
+}
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..8adbd5a79
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..b3287bd84
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_select_character.png b/dasherdancer/src/main/res/drawable-hdpi/btn_select_character.png
new file mode 100644
index 000000000..ad9d15a74
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_select_character.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_select_character_pressed.png b/dasherdancer/src/main/res/drawable-hdpi/btn_select_character_pressed.png
new file mode 100644
index 000000000..3ac4e48b5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_select_character_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_x_out.png b/dasherdancer/src/main/res/drawable-hdpi/btn_x_out.png
new file mode 100644
index 000000000..0c07d16cb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_x_out.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/btn_x_out_pressed.png b/dasherdancer/src/main/res/drawable-hdpi/btn_x_out_pressed.png
new file mode 100644
index 000000000..e8fbe8fe8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/btn_x_out_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..63fddf6e5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_left.png b/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_left.png
new file mode 100644
index 000000000..36ba0582e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_left.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_right.png b/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_right.png
new file mode 100644
index 000000000..22e17faaa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/icn_arrow_right.png differ
diff --git a/dasherdancer/src/main/res/drawable-hdpi/img_mint_loading_spinner.png b/dasherdancer/src/main/res/drawable-hdpi/img_mint_loading_spinner.png
new file mode 100644
index 000000000..ad10a84c1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-hdpi/img_mint_loading_spinner.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..a8223660d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..012ec450c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_select_character.png b/dasherdancer/src/main/res/drawable-mdpi/btn_select_character.png
new file mode 100644
index 000000000..07c2b2425
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_select_character.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_select_character_pressed.png b/dasherdancer/src/main/res/drawable-mdpi/btn_select_character_pressed.png
new file mode 100644
index 000000000..d3007b335
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_select_character_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_x_out.png b/dasherdancer/src/main/res/drawable-mdpi/btn_x_out.png
new file mode 100644
index 000000000..173dd9af4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_x_out.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/btn_x_out_pressed.png b/dasherdancer/src/main/res/drawable-mdpi/btn_x_out_pressed.png
new file mode 100644
index 000000000..a3aa8a45f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/btn_x_out_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..69e443b8c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_left.png b/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_left.png
new file mode 100644
index 000000000..cf2884ffb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_left.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_right.png b/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_right.png
new file mode 100644
index 000000000..47e24e564
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/icn_arrow_right.png differ
diff --git a/dasherdancer/src/main/res/drawable-mdpi/img_mint_loading_spinner.png b/dasherdancer/src/main/res/drawable-mdpi/img_mint_loading_spinner.png
new file mode 100644
index 000000000..45d0fdcfe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-mdpi/img_mint_loading_spinner.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0001.png
new file mode 100644
index 000000000..621bf3fe9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0002.png
new file mode 100644
index 000000000..5b032ba21
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0003.png
new file mode 100644
index 000000000..78df727b6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0004.png
new file mode 100644
index 000000000..d06f5bb0c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0005.png
new file mode 100644
index 000000000..96a5b2cee
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0006.png
new file mode 100644
index 000000000..ab93fb42c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0007.png
new file mode 100644
index 000000000..a9cc24e3e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0008.png
new file mode 100644
index 000000000..28fe82578
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0009.png
new file mode 100644
index 000000000..92f75957d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0010.png
new file mode 100644
index 000000000..4035690fe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0011.png
new file mode 100644
index 000000000..6be2ad95d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0012.png
new file mode 100644
index 000000000..4cb6b311a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0013.png
new file mode 100644
index 000000000..6be2ad95d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0014.png
new file mode 100644
index 000000000..4035690fe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0015.png
new file mode 100644
index 000000000..3fb7ecafd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0016.png
new file mode 100644
index 000000000..92f75957d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0017.png
new file mode 100644
index 000000000..bc91b74b8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0018.png
new file mode 100644
index 000000000..a9cc24e3e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0019.png
new file mode 100644
index 000000000..3f1c99136
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0020.png
new file mode 100644
index 000000000..b85944579
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0021.png
new file mode 100644
index 000000000..37464b038
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0022.png
new file mode 100644
index 000000000..f93e3a806
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0023.png
new file mode 100644
index 000000000..5b032ba21
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_idle0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0024.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_idle0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0001.png
new file mode 100644
index 000000000..908d5e464
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0002.png
new file mode 100644
index 000000000..6b7416753
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0003.png
new file mode 100644
index 000000000..f065f5455
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0004.png
new file mode 100644
index 000000000..cb048ba00
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0005.png
new file mode 100644
index 000000000..2d07753bf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0006.png
new file mode 100644
index 000000000..6bce31a51
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0007.png
new file mode 100644
index 000000000..51261dfd4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0008.png
new file mode 100644
index 000000000..1b383a8dd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0009.png
new file mode 100644
index 000000000..ab49ad520
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0010.png
new file mode 100644
index 000000000..bbdcd4439
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0011.png
new file mode 100644
index 000000000..ee6cbbb41
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0012.png
new file mode 100644
index 000000000..e3f1541cf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0013.png
new file mode 100644
index 000000000..a5f464b28
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0014.png
new file mode 100644
index 000000000..6f8cb9a26
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0015.png
new file mode 100644
index 000000000..95157c370
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0016.png
new file mode 100644
index 000000000..333a01f18
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0017.png
new file mode 100644
index 000000000..a8e3e235b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0018.png
new file mode 100644
index 000000000..595400797
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0019.png
new file mode 100644
index 000000000..ebe25c7d7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0020.png
new file mode 100644
index 000000000..917f659e0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0021.png
new file mode 100644
index 000000000..40c6a4692
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0022.png
new file mode 100644
index 000000000..04f6eafb4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0023.png
new file mode 100644
index 000000000..e2939bc15
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0024.png
new file mode 100644
index 000000000..934316592
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0025.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0025.png
new file mode 100644
index 000000000..d8fd29edf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0025.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0026.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0026.png
new file mode 100644
index 000000000..c46c1e9af
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0026.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0027.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0027.png
new file mode 100644
index 000000000..3818c7865
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0027.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0028.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0028.png
new file mode 100644
index 000000000..d0dc54e35
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0028.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0029.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0029.png
new file mode 100644
index 000000000..cb8bee0dd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0029.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0030.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0030.png
new file mode 100644
index 000000000..9bd7dfdb3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0030.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0031.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0031.png
new file mode 100644
index 000000000..1081a6ce6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0031.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0032.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0032.png
new file mode 100644
index 000000000..ebbfe0fa6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchin_ball0032.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0001.png
new file mode 100644
index 000000000..574dad317
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0002.png
new file mode 100644
index 000000000..1b62088d9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0003.png
new file mode 100644
index 000000000..0a3555ecb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0004.png
new file mode 100644
index 000000000..f830acc38
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0005.png
new file mode 100644
index 000000000..8cdbe12a2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0006.png
new file mode 100644
index 000000000..0385480c6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0007.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0008.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0009.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0010.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0011.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0012.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0013.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0014.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0015.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0016.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0017.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0018.png
new file mode 100644
index 000000000..2999b0510
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0019.png
new file mode 100644
index 000000000..d917dc91f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0020.png
new file mode 100644
index 000000000..4bb4408fe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0021.png
new file mode 100644
index 000000000..f3699e2b2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0022.png
new file mode 100644
index 000000000..d6eb12ad1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0023.png
new file mode 100644
index 000000000..30b88baa2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0024.png
new file mode 100644
index 000000000..e8aa47e39
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_pinchout0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0001.png
new file mode 100644
index 000000000..995bd0b79
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0002.png
new file mode 100644
index 000000000..7b535b22d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0003.png
new file mode 100644
index 000000000..e0cab467f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0004.png
new file mode 100644
index 000000000..4818f79aa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0005.png
new file mode 100644
index 000000000..8a552da60
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0006.png
new file mode 100644
index 000000000..0341331be
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0007.png
new file mode 100644
index 000000000..7d54c3c73
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0008.png
new file mode 100644
index 000000000..1b4961c81
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0009.png
new file mode 100644
index 000000000..8ab076220
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0010.png
new file mode 100644
index 000000000..1bf77a142
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0011.png
new file mode 100644
index 000000000..284b1a5df
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0012.png
new file mode 100644
index 000000000..5b7c199cf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0013.png
new file mode 100644
index 000000000..78b3bbd6e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0014.png
new file mode 100644
index 000000000..c9ad0c741
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0015.png
new file mode 100644
index 000000000..43370b68b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0016.png
new file mode 100644
index 000000000..75a8b07ef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0017.png
new file mode 100644
index 000000000..112711199
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0018.png
new file mode 100644
index 000000000..63d6cca85
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0019.png
new file mode 100644
index 000000000..36f6e4d65
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0020.png
new file mode 100644
index 000000000..4ef227643
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0021.png
new file mode 100644
index 000000000..3fb31afdc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0022.png
new file mode 100644
index 000000000..79fe62d02
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0023.png
new file mode 100644
index 000000000..a3b1e4f6e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_shake0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0024.png
new file mode 100644
index 000000000..995bd0b79
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_shake0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0001.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0002.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0003.png
new file mode 100644
index 000000000..2684ceaad
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0004.png
new file mode 100644
index 000000000..71aafff24
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0005.png
new file mode 100644
index 000000000..a73c2f7c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0006.png
new file mode 100644
index 000000000..57bc51cc8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0007.png
new file mode 100644
index 000000000..6fd0a821b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0008.png
new file mode 100644
index 000000000..07d509d3f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0009.png
new file mode 100644
index 000000000..43c6c6054
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0010.png
new file mode 100644
index 000000000..f0e359804
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0011.png
new file mode 100644
index 000000000..6480b5922
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0012.png
new file mode 100644
index 000000000..607d6ad08
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0013.png
new file mode 100644
index 000000000..18c720550
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0014.png
new file mode 100644
index 000000000..607d6ad08
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0015.png
new file mode 100644
index 000000000..20b16ced0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0016.png
new file mode 100644
index 000000000..21fd456af
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0017.png
new file mode 100644
index 000000000..bc7f2b711
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0018.png
new file mode 100644
index 000000000..9af289f23
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0019.png
new file mode 100644
index 000000000..7ebf72e0e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0020.png
new file mode 100644
index 000000000..80d361e0c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0021.png
new file mode 100644
index 000000000..dce325e68
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0022.png
new file mode 100644
index 000000000..46d6514b4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0023.png
new file mode 100644
index 000000000..ded5d1fa9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0024.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipedown0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0001.png
new file mode 100644
index 000000000..4f21e029f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0002.png
new file mode 100644
index 000000000..38959c3e2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0003.png
new file mode 100644
index 000000000..c011059ed
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0004.png
new file mode 100644
index 000000000..3aa531f94
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0005.png
new file mode 100644
index 000000000..6b5d74791
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0006.png
new file mode 100644
index 000000000..d5aff98cc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0007.png
new file mode 100644
index 000000000..94bd587fa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0008.png
new file mode 100644
index 000000000..0af1342f4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0009.png
new file mode 100644
index 000000000..7cda28487
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0010.png
new file mode 100644
index 000000000..1f987632f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0011.png
new file mode 100644
index 000000000..31b0451a7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0012.png
new file mode 100644
index 000000000..c58c11a9f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0013.png
new file mode 100644
index 000000000..c2d445643
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0014.png
new file mode 100644
index 000000000..392d3f55b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0015.png
new file mode 100644
index 000000000..e37e17aa8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0016.png
new file mode 100644
index 000000000..5f4340ba4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0017.png
new file mode 100644
index 000000000..91ccdb925
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0018.png
new file mode 100644
index 000000000..17eca8553
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0019.png
new file mode 100644
index 000000000..b5f0495e1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0020.png
new file mode 100644
index 000000000..c07361a4b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0021.png
new file mode 100644
index 000000000..47f4c9cc0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0022.png
new file mode 100644
index 000000000..0764e73a2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0023.png
new file mode 100644
index 000000000..225cf224e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0024.png
new file mode 100644
index 000000000..7dc00269d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeleft0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0001.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0002.png
new file mode 100644
index 000000000..6853f69e0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0003.png
new file mode 100644
index 000000000..29602c492
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0004.png
new file mode 100644
index 000000000..d8321aac6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0005.png
new file mode 100644
index 000000000..7160a4927
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0006.png
new file mode 100644
index 000000000..19af0c4f3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0007.png
new file mode 100644
index 000000000..e021c5ad4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0008.png
new file mode 100644
index 000000000..f5a46a69b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0009.png
new file mode 100644
index 000000000..519e907ee
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0010.png
new file mode 100644
index 000000000..39c1e787f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0011.png
new file mode 100644
index 000000000..d7b2f9fae
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0012.png
new file mode 100644
index 000000000..1309106bd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0013.png
new file mode 100644
index 000000000..f212a3a97
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0014.png
new file mode 100644
index 000000000..2040054ba
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0015.png
new file mode 100644
index 000000000..4f6c7d842
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0016.png
new file mode 100644
index 000000000..23f8f1959
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0017.png
new file mode 100644
index 000000000..880f02f38
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0018.png
new file mode 100644
index 000000000..4f103a178
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0019.png
new file mode 100644
index 000000000..c8c2af3ae
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0020.png
new file mode 100644
index 000000000..26b52aec2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0021.png
new file mode 100644
index 000000000..fe2eb30a0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0022.png
new file mode 100644
index 000000000..36f31eee7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0023.png
new file mode 100644
index 000000000..0380fbe24
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0024.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swiperight0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0001.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0002.png
new file mode 100644
index 000000000..1d57e3626
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0003.png
new file mode 100644
index 000000000..3cfd41a38
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0004.png
new file mode 100644
index 000000000..9f638e37a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0005.png
new file mode 100644
index 000000000..e47e5c521
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0006.png
new file mode 100644
index 000000000..695faa88a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0007.png
new file mode 100644
index 000000000..c84b7557b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0008.png
new file mode 100644
index 000000000..e1d6221d5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0009.png
new file mode 100644
index 000000000..7ccea8b23
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0010.png
new file mode 100644
index 000000000..e3ccda35d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0011.png
new file mode 100644
index 000000000..5cc80843e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0012.png
new file mode 100644
index 000000000..b19ba8400
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0013.png
new file mode 100644
index 000000000..634c8d7c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0014.png
new file mode 100644
index 000000000..ff68c2f24
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0015.png
new file mode 100644
index 000000000..05b59097f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0016.png
new file mode 100644
index 000000000..d4cee897e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0017.png
new file mode 100644
index 000000000..4c0a70cc8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0018.png
new file mode 100644
index 000000000..501a1067a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0019.png
new file mode 100644
index 000000000..826acff7a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0020.png
new file mode 100644
index 000000000..b10414eba
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0021.png
new file mode 100644
index 000000000..c4361924c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0022.png
new file mode 100644
index 000000000..123161f94
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0023.png
new file mode 100644
index 000000000..dddf711b0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0024.png
new file mode 100644
index 000000000..6c015a13f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_swipeup0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0001.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0001.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0002.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0002.png
new file mode 100644
index 000000000..5dd751adc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0003.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0003.png
new file mode 100644
index 000000000..b1d5d528a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0004.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0004.png
new file mode 100644
index 000000000..fdf4a6389
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0005.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0005.png
new file mode 100644
index 000000000..65a5dc934
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0006.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0006.png
new file mode 100644
index 000000000..83a7a34f1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0007.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0007.png
new file mode 100644
index 000000000..a217e428e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0008.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0008.png
new file mode 100644
index 000000000..2a905d350
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0009.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0009.png
new file mode 100644
index 000000000..47e8e1827
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0010.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0010.png
new file mode 100644
index 000000000..0d585b2b1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0011.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0011.png
new file mode 100644
index 000000000..38ad8e972
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0012.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0012.png
new file mode 100644
index 000000000..9d5efd509
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0013.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0013.png
new file mode 100644
index 000000000..42bfac5a8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0014.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0014.png
new file mode 100644
index 000000000..efe5bb92a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0015.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0015.png
new file mode 100644
index 000000000..19a995fdc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0016.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0016.png
new file mode 100644
index 000000000..e9e7bf8c8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0017.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0017.png
new file mode 100644
index 000000000..b85340d40
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0018.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0018.png
new file mode 100644
index 000000000..648514c89
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0019.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0019.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0020.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0020.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0021.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0021.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0022.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0022.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0023.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0023.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/elf_tap0024.png b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0024.png
new file mode 100644
index 000000000..e0d068825
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/elf_tap0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer.png
new file mode 100644
index 000000000..2c58a3006
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0001.png
new file mode 100644
index 000000000..2cedb55dc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0002.png
new file mode 100644
index 000000000..1149dbacb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0003.png
new file mode 100644
index 000000000..b3d9869bb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0004.png
new file mode 100644
index 000000000..64e4a54fb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0005.png
new file mode 100644
index 000000000..d824c90cd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0006.png
new file mode 100644
index 000000000..958a041e5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0007.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0008.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0009.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0010.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0011.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0012.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0013.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0014.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0015.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0016.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0017.png
new file mode 100644
index 000000000..8dbcfb53b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0018.png
new file mode 100644
index 000000000..e3c49f3f1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0019.png
new file mode 100644
index 000000000..52c3f3362
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0020.png
new file mode 100644
index 000000000..b5adfacab
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0021.png
new file mode 100644
index 000000000..43eac7e6d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0022.png
new file mode 100644
index 000000000..1bad9963c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0023.png
new file mode 100644
index 000000000..4111d37c1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0024.png
new file mode 100644
index 000000000..2cedb55dc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchin0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0001.png
new file mode 100644
index 000000000..830e7ec19
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0002.png
new file mode 100644
index 000000000..e46105e42
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0003.png
new file mode 100644
index 000000000..4839b18dc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0004.png
new file mode 100644
index 000000000..bf07ed93e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0005.png
new file mode 100644
index 000000000..93a997407
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0006.png
new file mode 100644
index 000000000..7fc12dcd3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0007.png
new file mode 100644
index 000000000..32cd889b8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0008.png
new file mode 100644
index 000000000..1f90e40c2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0009.png
new file mode 100644
index 000000000..833c55ba9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0010.png
new file mode 100644
index 000000000..8f75a31ad
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0011.png
new file mode 100644
index 000000000..9c52013d5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0012.png
new file mode 100644
index 000000000..33f09b80d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0013.png
new file mode 100644
index 000000000..23f28d835
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0014.png
new file mode 100644
index 000000000..d21b17eca
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0015.png
new file mode 100644
index 000000000..0efb5086c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0016.png
new file mode 100644
index 000000000..d6a3f4b50
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0017.png
new file mode 100644
index 000000000..c1c32baaa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0018.png
new file mode 100644
index 000000000..f620c53a8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0019.png
new file mode 100644
index 000000000..4a5d25b49
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0020.png
new file mode 100644
index 000000000..0e9742247
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0021.png
new file mode 100644
index 000000000..3d9af5468
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0022.png
new file mode 100644
index 000000000..1dcbcc7b7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0023.png
new file mode 100644
index 000000000..0f188bf7f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0024.png
new file mode 100644
index 000000000..2ef561eba
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_pinchout0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0001.png
new file mode 100644
index 000000000..830e7ec19
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0002.png
new file mode 100644
index 000000000..4594b943e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0003.png
new file mode 100644
index 000000000..c83543cb6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0004.png
new file mode 100644
index 000000000..814b93848
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0005.png
new file mode 100644
index 000000000..2a715538e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0006.png
new file mode 100644
index 000000000..a264994c8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0007.png
new file mode 100644
index 000000000..0429b1936
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0008.png
new file mode 100644
index 000000000..18a2a34cc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0009.png
new file mode 100644
index 000000000..88e2bfe36
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0010.png
new file mode 100644
index 000000000..efda24d8d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0011.png
new file mode 100644
index 000000000..5482728dc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0012.png
new file mode 100644
index 000000000..9cbb19786
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0013.png
new file mode 100644
index 000000000..4bf71379e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0014.png
new file mode 100644
index 000000000..ec16c4e17
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0015.png
new file mode 100644
index 000000000..7b53fec0c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0016.png
new file mode 100644
index 000000000..ccbfa9b96
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0017.png
new file mode 100644
index 000000000..cc6e34a0b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0018.png
new file mode 100644
index 000000000..0aafee8ee
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0019.png
new file mode 100644
index 000000000..9b07dd74a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0020.png
new file mode 100644
index 000000000..89103de3e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0021.png
new file mode 100644
index 000000000..7b54ef65e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0022.png
new file mode 100644
index 000000000..30031462f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0023.png
new file mode 100644
index 000000000..7567ca448
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0024.png
new file mode 100644
index 000000000..84dab29eb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_shake0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0001.png
new file mode 100644
index 000000000..7ee857bf1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0002.png
new file mode 100644
index 000000000..18f90ec03
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0003.png
new file mode 100644
index 000000000..87e47c927
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0004.png
new file mode 100644
index 000000000..6d224b4fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0005.png
new file mode 100644
index 000000000..e97e9ecef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0006.png
new file mode 100644
index 000000000..e54f86e6a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0007.png
new file mode 100644
index 000000000..48e8322bb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0008.png
new file mode 100644
index 000000000..34ae3f298
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0009.png
new file mode 100644
index 000000000..e7e360688
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0010.png
new file mode 100644
index 000000000..88535ab0b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0011.png
new file mode 100644
index 000000000..8e255b902
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0012.png
new file mode 100644
index 000000000..54e841f0c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0013.png
new file mode 100644
index 000000000..65d2f1eef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0014.png
new file mode 100644
index 000000000..97d3f55ce
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0015.png
new file mode 100644
index 000000000..8608a2ddd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0016.png
new file mode 100644
index 000000000..5a7498679
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0017.png
new file mode 100644
index 000000000..75c86b9c5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0018.png
new file mode 100644
index 000000000..86b47538f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0019.png
new file mode 100644
index 000000000..04c722a93
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0020.png
new file mode 100644
index 000000000..45e3b6894
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0021.png
new file mode 100644
index 000000000..ca44daada
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0022.png
new file mode 100644
index 000000000..5c1dcb057
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0023.png
new file mode 100644
index 000000000..673a0bd61
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0024.png
new file mode 100644
index 000000000..7ee857bf1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipedown0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0001.png
new file mode 100644
index 000000000..5ba59356c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0002.png
new file mode 100644
index 000000000..e2856daf1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0003.png
new file mode 100644
index 000000000..70cb533cc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0004.png
new file mode 100644
index 000000000..49817f5ea
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0005.png
new file mode 100644
index 000000000..9d19bda8d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0006.png
new file mode 100644
index 000000000..a06978f25
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0007.png
new file mode 100644
index 000000000..cd3d22836
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0008.png
new file mode 100644
index 000000000..70f84a1f1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0009.png
new file mode 100644
index 000000000..edefd69a4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0010.png
new file mode 100644
index 000000000..4afde9c36
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0011.png
new file mode 100644
index 000000000..5587d4c3e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0012.png
new file mode 100644
index 000000000..b14f1c889
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0013.png
new file mode 100644
index 000000000..c6551763f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0014.png
new file mode 100644
index 000000000..5dec7ae46
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0015.png
new file mode 100644
index 000000000..91e65169e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0016.png
new file mode 100644
index 000000000..9f675e46f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0017.png
new file mode 100644
index 000000000..7c2c129d0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0018.png
new file mode 100644
index 000000000..29dd9b771
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0019.png
new file mode 100644
index 000000000..339a577c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0020.png
new file mode 100644
index 000000000..42182f2ec
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0021.png
new file mode 100644
index 000000000..8842f06a6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0022.png
new file mode 100644
index 000000000..5a7aa4b69
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0023.png
new file mode 100644
index 000000000..99264dfdd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0024.png
new file mode 100644
index 000000000..fe22f7718
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeleft0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0001.png
new file mode 100644
index 000000000..982154340
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0002.png
new file mode 100644
index 000000000..bc8068d5d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0003.png
new file mode 100644
index 000000000..549240003
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0004.png
new file mode 100644
index 000000000..a7ddb6ce9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0005.png
new file mode 100644
index 000000000..46db354b1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0006.png
new file mode 100644
index 000000000..f3bcda2f8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0007.png
new file mode 100644
index 000000000..fce370011
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0008.png
new file mode 100644
index 000000000..574e0702d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0009.png
new file mode 100644
index 000000000..569c3815f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0010.png
new file mode 100644
index 000000000..f276e09ba
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0011.png
new file mode 100644
index 000000000..146a1757a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0012.png
new file mode 100644
index 000000000..52b0c4b9f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0013.png
new file mode 100644
index 000000000..14ba9bfae
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0014.png
new file mode 100644
index 000000000..92d26654b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0015.png
new file mode 100644
index 000000000..edde12d31
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0016.png
new file mode 100644
index 000000000..27f08791d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0017.png
new file mode 100644
index 000000000..eaaf575b6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0018.png
new file mode 100644
index 000000000..56429a9d1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0019.png
new file mode 100644
index 000000000..c74ee5812
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0020.png
new file mode 100644
index 000000000..67e41e2d7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0021.png
new file mode 100644
index 000000000..c9aece9f2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0022.png
new file mode 100644
index 000000000..dc43e63c9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0023.png
new file mode 100644
index 000000000..6963c23cc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0024.png
new file mode 100644
index 000000000..1899123e2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swiperight0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0001.png
new file mode 100644
index 000000000..d64816178
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0002.png
new file mode 100644
index 000000000..57a84edbf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0003.png
new file mode 100644
index 000000000..f48d1fc48
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0004.png
new file mode 100644
index 000000000..241d5a35a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0005.png
new file mode 100644
index 000000000..46f0ed0b7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0006.png
new file mode 100644
index 000000000..98f952071
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0007.png
new file mode 100644
index 000000000..dc5600df5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0008.png
new file mode 100644
index 000000000..42100e713
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0009.png
new file mode 100644
index 000000000..492e60dfc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0010.png
new file mode 100644
index 000000000..9bd1330d3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0011.png
new file mode 100644
index 000000000..6e566c090
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0012.png
new file mode 100644
index 000000000..ade635c49
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0013.png
new file mode 100644
index 000000000..e80f37ea7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0014.png
new file mode 100644
index 000000000..4a1c1afd7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0015.png
new file mode 100644
index 000000000..3561ef789
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0016.png
new file mode 100644
index 000000000..eeaebdc47
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0017.png
new file mode 100644
index 000000000..c368484c3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0018.png
new file mode 100644
index 000000000..bb89d546e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0019.png
new file mode 100644
index 000000000..0b389636f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0020.png
new file mode 100644
index 000000000..7de27dace
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0021.png
new file mode 100644
index 000000000..c236bb770
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0022.png
new file mode 100644
index 000000000..9ea3b715b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0023.png
new file mode 100644
index 000000000..af8da2178
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0024.png
new file mode 100644
index 000000000..d64816178
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_swipeup0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0001.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0001.png
new file mode 100644
index 000000000..8ff93c534
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0002.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0002.png
new file mode 100644
index 000000000..4d4a01f6b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0003.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0003.png
new file mode 100644
index 000000000..dbe41b1fb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0004.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0004.png
new file mode 100644
index 000000000..a95c86df7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0005.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0005.png
new file mode 100644
index 000000000..7abea52b2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0006.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0006.png
new file mode 100644
index 000000000..ec5840be0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0007.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0007.png
new file mode 100644
index 000000000..374c9b36b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0008.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0008.png
new file mode 100644
index 000000000..d82439ca0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0009.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0009.png
new file mode 100644
index 000000000..b3e6b2863
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0010.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0010.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0011.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0011.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0012.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0012.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0013.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0013.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0014.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0014.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0015.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0015.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0016.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0016.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0017.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0017.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0018.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0018.png
new file mode 100644
index 000000000..879518dcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0019.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0019.png
new file mode 100644
index 000000000..33de42a1c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0020.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0020.png
new file mode 100644
index 000000000..66115d214
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0021.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0021.png
new file mode 100644
index 000000000..960e98481
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0022.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0022.png
new file mode 100644
index 000000000..73b97d383
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0023.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0023.png
new file mode 100644
index 000000000..7abea52b2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0024.png b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0024.png
new file mode 100644
index 000000000..7abea52b2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/reindeer_tap0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa.png b/dasherdancer/src/main/res/drawable-nodpi/santa.png
new file mode 100644
index 000000000..3a841f400
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0001.png
new file mode 100644
index 000000000..102722b18
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0002.png
new file mode 100644
index 000000000..9f9fc55ed
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0003.png
new file mode 100644
index 000000000..82a4c5c9e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0004.png
new file mode 100644
index 000000000..1f4a08467
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0005.png
new file mode 100644
index 000000000..c5e523443
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0006.png
new file mode 100644
index 000000000..85c89a641
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0007.png
new file mode 100644
index 000000000..4d798347e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0008.png
new file mode 100644
index 000000000..3fd4d9606
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0009.png
new file mode 100644
index 000000000..3572acaf8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0010.png
new file mode 100644
index 000000000..af84d9629
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0011.png
new file mode 100644
index 000000000..e39def95e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0012.png
new file mode 100644
index 000000000..013f016ef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0013.png
new file mode 100644
index 000000000..06ed3ac9e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0014.png
new file mode 100644
index 000000000..3fd4d9606
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0015.png
new file mode 100644
index 000000000..ceee35f70
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0016.png
new file mode 100644
index 000000000..b4042be37
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0017.png
new file mode 100644
index 000000000..1adf5a89b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0018.png
new file mode 100644
index 000000000..989f81194
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0019.png
new file mode 100644
index 000000000..e85baea3b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0020.png
new file mode 100644
index 000000000..34a58af74
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0021.png
new file mode 100644
index 000000000..4d3dc9274
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0022.png
new file mode 100644
index 000000000..ea1447017
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0023.png
new file mode 100644
index 000000000..102722b18
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0024.png
new file mode 100644
index 000000000..102722b18
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0025.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0025.png
new file mode 100644
index 000000000..9f9fc55ed
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0025.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0026.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0026.png
new file mode 100644
index 000000000..82a4c5c9e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0026.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0027.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0027.png
new file mode 100644
index 000000000..9b18d9452
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0027.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0028.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0028.png
new file mode 100644
index 000000000..01c33d262
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0028.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0029.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0029.png
new file mode 100644
index 000000000..2e78fadae
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0029.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0030.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0030.png
new file mode 100644
index 000000000..10472432f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0030.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0031.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0031.png
new file mode 100644
index 000000000..533cf7729
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0031.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0032.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0032.png
new file mode 100644
index 000000000..3fab52715
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0032.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0033.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0033.png
new file mode 100644
index 000000000..b89ea34d9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0033.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0034.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0034.png
new file mode 100644
index 000000000..e39def95e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0034.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0035.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0035.png
new file mode 100644
index 000000000..013f016ef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0035.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0036.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0036.png
new file mode 100644
index 000000000..2e43de83b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0036.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0037.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0037.png
new file mode 100644
index 000000000..2197b3bab
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0037.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0038.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0038.png
new file mode 100644
index 000000000..cde8afd61
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0038.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0039.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0039.png
new file mode 100644
index 000000000..06b86d4fd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0039.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0040.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0040.png
new file mode 100644
index 000000000..b9af1764b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0040.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0041.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0041.png
new file mode 100644
index 000000000..679f6b5c2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0041.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0042.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0042.png
new file mode 100644
index 000000000..700492ad3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0042.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0043.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0043.png
new file mode 100644
index 000000000..86115612c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0043.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0044.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0044.png
new file mode 100644
index 000000000..02987dc27
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0044.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0045.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0045.png
new file mode 100644
index 000000000..b89ec1801
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0045.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0046.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0046.png
new file mode 100644
index 000000000..2727003ce
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0046.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0047.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0047.png
new file mode 100644
index 000000000..82d3a741a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0047.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_idle0048.png b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0048.png
new file mode 100644
index 000000000..102722b18
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_idle0048.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0001.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0002.png
new file mode 100644
index 000000000..21bca6749
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0003.png
new file mode 100644
index 000000000..a54326bd7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0004.png
new file mode 100644
index 000000000..fddb8394a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0005.png
new file mode 100644
index 000000000..f12f2a404
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0006.png
new file mode 100644
index 000000000..daef24b46
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0007.png
new file mode 100644
index 000000000..17fdee8fe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0008.png
new file mode 100644
index 000000000..7a1fc1629
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0009.png
new file mode 100644
index 000000000..a7d6ef94e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0010.png
new file mode 100644
index 000000000..3fca71da3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0011.png
new file mode 100644
index 000000000..a7d6ef94e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0012.png
new file mode 100644
index 000000000..7a1fc1629
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0013.png
new file mode 100644
index 000000000..dbe2a5aa1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0014.png
new file mode 100644
index 000000000..479e0172c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0015.png
new file mode 100644
index 000000000..f12f2a404
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0016.png
new file mode 100644
index 000000000..fddb8394a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0017.png
new file mode 100644
index 000000000..2f8b96e1c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0018.png
new file mode 100644
index 000000000..208be4a9a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0019.png
new file mode 100644
index 000000000..875c78baa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0020.png
new file mode 100644
index 000000000..1145d6584
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0021.png
new file mode 100644
index 000000000..2f8b96e1c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0022.png
new file mode 100644
index 000000000..ee3e61751
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0023.png
new file mode 100644
index 000000000..21bca6749
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0024.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchin0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20001.png
new file mode 100644
index 000000000..c9aae9f64
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20002.png
new file mode 100644
index 000000000..56d246020
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20003.png
new file mode 100644
index 000000000..174b81809
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20004.png
new file mode 100644
index 000000000..d0099e780
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20005.png
new file mode 100644
index 000000000..14cc6880c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20006.png
new file mode 100644
index 000000000..54e897363
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20007.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20008.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20009.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20010.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20011.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20012.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20013.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20014.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20015.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20016.png
new file mode 100644
index 000000000..4a3840dcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20017.png
new file mode 100644
index 000000000..aa712e8f1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20018.png
new file mode 100644
index 000000000..88d88d15c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20019.png
new file mode 100644
index 000000000..eb122aea6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20020.png
new file mode 100644
index 000000000..ea436c4d9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20021.png
new file mode 100644
index 000000000..03e41b506
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20022.png
new file mode 100644
index 000000000..c9aae9f64
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20023.png
new file mode 100644
index 000000000..60e96c96d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20024.png
new file mode 100644
index 000000000..60e96c96d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_pinchout20024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0001.png
new file mode 100644
index 000000000..778276d99
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0002.png
new file mode 100644
index 000000000..af3b404c4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0003.png
new file mode 100644
index 000000000..5b5098769
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0004.png
new file mode 100644
index 000000000..106621aed
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0005.png
new file mode 100644
index 000000000..7623f11b7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0006.png
new file mode 100644
index 000000000..d5dedecb1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0007.png
new file mode 100644
index 000000000..54e805a37
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0008.png
new file mode 100644
index 000000000..be5e55303
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0009.png
new file mode 100644
index 000000000..db142d92c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0010.png
new file mode 100644
index 000000000..e05e09b1d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0011.png
new file mode 100644
index 000000000..ae9893a13
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0012.png
new file mode 100644
index 000000000..def5a9fcd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0013.png
new file mode 100644
index 000000000..3fb9bdfa5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0014.png
new file mode 100644
index 000000000..4910d2156
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0015.png
new file mode 100644
index 000000000..9557cc69e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0016.png
new file mode 100644
index 000000000..570bdeb52
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0017.png
new file mode 100644
index 000000000..d93d78a05
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0018.png
new file mode 100644
index 000000000..a982fefa3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0019.png
new file mode 100644
index 000000000..821e2ab95
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0020.png
new file mode 100644
index 000000000..4547b1ee3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0021.png
new file mode 100644
index 000000000..473a6e941
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0022.png
new file mode 100644
index 000000000..4521a781c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0023.png
new file mode 100644
index 000000000..0abe1e5c4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_shake0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0024.png
new file mode 100644
index 000000000..fe6814992
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_shake0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20001.png
new file mode 100644
index 000000000..97b489b38
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20002.png
new file mode 100644
index 000000000..b22a952a5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20003.png
new file mode 100644
index 000000000..4addeffaa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20004.png
new file mode 100644
index 000000000..28b1e6306
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20005.png
new file mode 100644
index 000000000..d8d638a42
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20006.png
new file mode 100644
index 000000000..2039bae01
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20007.png
new file mode 100644
index 000000000..a6838a7ed
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20008.png
new file mode 100644
index 000000000..6e98cf2c5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20009.png
new file mode 100644
index 000000000..934941169
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20010.png
new file mode 100644
index 000000000..90ff48772
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20011.png
new file mode 100644
index 000000000..8dcaa68f4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20012.png
new file mode 100644
index 000000000..35bf196c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20013.png
new file mode 100644
index 000000000..3aeeef3d5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20014.png
new file mode 100644
index 000000000..7948cc674
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20015.png
new file mode 100644
index 000000000..23200c132
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20016.png
new file mode 100644
index 000000000..e8b87690b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20017.png
new file mode 100644
index 000000000..30435cdbc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20018.png
new file mode 100644
index 000000000..0ba926107
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20019.png
new file mode 100644
index 000000000..4c73f421f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20020.png
new file mode 100644
index 000000000..7680c785a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20021.png
new file mode 100644
index 000000000..131b6515b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20022.png
new file mode 100644
index 000000000..15eb5fa1b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20023.png
new file mode 100644
index 000000000..07dd2edc0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20024.png
new file mode 100644
index 000000000..2ee120091
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipe_right20024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0001.png
new file mode 100644
index 000000000..4be7d4ebf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0002.png
new file mode 100644
index 000000000..76596cc99
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0003.png
new file mode 100644
index 000000000..2ad329636
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0004.png
new file mode 100644
index 000000000..1b6c5d225
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0005.png
new file mode 100644
index 000000000..e11a8aa87
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0006.png
new file mode 100644
index 000000000..88e4bc07a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0007.png
new file mode 100644
index 000000000..f75588a2a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0008.png
new file mode 100644
index 000000000..34186f016
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0009.png
new file mode 100644
index 000000000..fb4ea9246
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0010.png
new file mode 100644
index 000000000..e90d5263e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0011.png
new file mode 100644
index 000000000..58cb7e3a4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0012.png
new file mode 100644
index 000000000..0e3f870fe
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0013.png
new file mode 100644
index 000000000..26b0c828f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0014.png
new file mode 100644
index 000000000..1a0a66110
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0015.png
new file mode 100644
index 000000000..0406d0382
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0016.png
new file mode 100644
index 000000000..d0ea832e6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0017.png
new file mode 100644
index 000000000..e1fc5d1a6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0018.png
new file mode 100644
index 000000000..2db882164
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0019.png
new file mode 100644
index 000000000..da73020e5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0020.png
new file mode 100644
index 000000000..bc46e5851
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0021.png
new file mode 100644
index 000000000..25f515b8a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0022.png
new file mode 100644
index 000000000..4162d244e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0023.png
new file mode 100644
index 000000000..25c367638
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0024.png
new file mode 100644
index 000000000..e7d1c2be2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipedown0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0001.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0002.png
new file mode 100644
index 000000000..7f2903769
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0003.png
new file mode 100644
index 000000000..6a8cc022d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0004.png
new file mode 100644
index 000000000..e0acb9987
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0005.png
new file mode 100644
index 000000000..057582c05
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0006.png
new file mode 100644
index 000000000..9d21e1197
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0007.png
new file mode 100644
index 000000000..0fdcd05fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0008.png
new file mode 100644
index 000000000..c790ac482
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0009.png
new file mode 100644
index 000000000..adf586712
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0010.png
new file mode 100644
index 000000000..0fdcd05fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0011.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0012.png
new file mode 100644
index 000000000..21419e3be
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0013.png
new file mode 100644
index 000000000..62604f651
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0014.png
new file mode 100644
index 000000000..bae14b90f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0015.png
new file mode 100644
index 000000000..6aa78c735
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0016.png
new file mode 100644
index 000000000..c7c273884
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0017.png
new file mode 100644
index 000000000..8d36347fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0018.png
new file mode 100644
index 000000000..6210d55fd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0019.png
new file mode 100644
index 000000000..c16779688
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0020.png
new file mode 100644
index 000000000..73a0c7874
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0021.png
new file mode 100644
index 000000000..be4ef2f80
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0022.png
new file mode 100644
index 000000000..281655078
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0023.png
new file mode 100644
index 000000000..39803863c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0024.png
new file mode 100644
index 000000000..eaa2eca85
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeleft0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0001.png
new file mode 100644
index 000000000..954d87a71
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0002.png
new file mode 100644
index 000000000..0143c40dc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0003.png
new file mode 100644
index 000000000..47a84854a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0004.png
new file mode 100644
index 000000000..c2053aa22
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0005.png
new file mode 100644
index 000000000..a248bb4a5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0006.png
new file mode 100644
index 000000000..8029858b0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0007.png
new file mode 100644
index 000000000..3c9d01ce9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0008.png
new file mode 100644
index 000000000..2988b8280
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0009.png
new file mode 100644
index 000000000..6389aab82
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0010.png
new file mode 100644
index 000000000..cbd109b3a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0011.png
new file mode 100644
index 000000000..c6b18bbcc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0012.png
new file mode 100644
index 000000000..c7509ad6b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0013.png
new file mode 100644
index 000000000..657d880b3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0014.png
new file mode 100644
index 000000000..0b57a0fe1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0015.png
new file mode 100644
index 000000000..3d2a65b56
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0016.png
new file mode 100644
index 000000000..fb04b6f00
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0017.png
new file mode 100644
index 000000000..5ab839edb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0018.png
new file mode 100644
index 000000000..644b2cbd4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0019.png
new file mode 100644
index 000000000..b51d9b301
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0020.png
new file mode 100644
index 000000000..002d8d30e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0021.png
new file mode 100644
index 000000000..932faeb6a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0022.png
new file mode 100644
index 000000000..0195e1cc0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0023.png
new file mode 100644
index 000000000..3709e5469
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0024.png
new file mode 100644
index 000000000..954d87a71
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_swipeup0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0001.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0001.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0002.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0002.png
new file mode 100644
index 000000000..21bca6749
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0003.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0003.png
new file mode 100644
index 000000000..43be5fded
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0004.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0004.png
new file mode 100644
index 000000000..b1692428f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0005.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0005.png
new file mode 100644
index 000000000..4c4cc8fef
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0006.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0006.png
new file mode 100644
index 000000000..d625e6cf3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0007.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0007.png
new file mode 100644
index 000000000..c63398e50
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0008.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0008.png
new file mode 100644
index 000000000..b2db5f1f5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0009.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0009.png
new file mode 100644
index 000000000..ab0601585
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0010.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0010.png
new file mode 100644
index 000000000..0149442c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0011.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0011.png
new file mode 100644
index 000000000..4750b3fc9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0012.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0012.png
new file mode 100644
index 000000000..6cde27909
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0013.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0013.png
new file mode 100644
index 000000000..ba4e67ff4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0014.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0014.png
new file mode 100644
index 000000000..0e63da339
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0015.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0015.png
new file mode 100644
index 000000000..d2da3f83e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0016.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0016.png
new file mode 100644
index 000000000..cd63c16ca
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0017.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0017.png
new file mode 100644
index 000000000..51d062151
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0018.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0018.png
new file mode 100644
index 000000000..c24efb737
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0019.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0019.png
new file mode 100644
index 000000000..af54faebd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0020.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0020.png
new file mode 100644
index 000000000..cd63c16ca
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0021.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0021.png
new file mode 100644
index 000000000..43be5fded
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0022.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0022.png
new file mode 100644
index 000000000..63c685b7c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0023.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0023.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_tap0024.png b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0024.png
new file mode 100644
index 000000000..08c890615
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_tap0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_temp_down01.png b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_down01.png
new file mode 100644
index 000000000..b0c2be127
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_down01.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_temp_left01.png b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_left01.png
new file mode 100644
index 000000000..cf8f2db40
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_left01.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_temp_shake01.png b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_shake01.png
new file mode 100644
index 000000000..3d38f6815
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_shake01.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_temp_tap01.png b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_tap01.png
new file mode 100644
index 000000000..ea45198e3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_tap01.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/santa_temp_zoom_in01.png b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_zoom_in01.png
new file mode 100644
index 000000000..c5507106c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/santa_temp_zoom_in01.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0001.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0002.png
new file mode 100644
index 000000000..0c3e6960e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0003.png
new file mode 100644
index 000000000..83355be7d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0004.png
new file mode 100644
index 000000000..49d294c2c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0005.png
new file mode 100644
index 000000000..12e3cbb3c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0006.png
new file mode 100644
index 000000000..f2abd8477
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0007.png
new file mode 100644
index 000000000..a0d27e5d6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0008.png
new file mode 100644
index 000000000..12de9ed3a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0009.png
new file mode 100644
index 000000000..f146291c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0010.png
new file mode 100644
index 000000000..300046251
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0011.png
new file mode 100644
index 000000000..89ef95ef1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0012.png
new file mode 100644
index 000000000..89ef95ef1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0013.png
new file mode 100644
index 000000000..89ef95ef1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0014.png
new file mode 100644
index 000000000..300046251
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0015.png
new file mode 100644
index 000000000..f146291c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0016.png
new file mode 100644
index 000000000..7689f8897
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0017.png
new file mode 100644
index 000000000..95090dfcf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0018.png
new file mode 100644
index 000000000..a956d7d56
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0019.png
new file mode 100644
index 000000000..ae39cc69b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0020.png
new file mode 100644
index 000000000..6b5afa8f8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0021.png
new file mode 100644
index 000000000..bc0534aa4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0022.png
new file mode 100644
index 000000000..4fba28a45
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0023.png
new file mode 100644
index 000000000..d19d70747
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0024.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_idle0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0001.png
new file mode 100644
index 000000000..486ae2471
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0002.png
new file mode 100644
index 000000000..d20f97f72
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0003.png
new file mode 100644
index 000000000..7e592d3fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0004.png
new file mode 100644
index 000000000..ec2562363
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0005.png
new file mode 100644
index 000000000..af168962e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0006.png
new file mode 100644
index 000000000..7a51a79a9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0007.png
new file mode 100644
index 000000000..6a940b77e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0008.png
new file mode 100644
index 000000000..0cddbb24d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0009.png
new file mode 100644
index 000000000..1a5811a17
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0010.png
new file mode 100644
index 000000000..ab9fa2211
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0011.png
new file mode 100644
index 000000000..472d011f4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0012.png
new file mode 100644
index 000000000..0ba683011
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0013.png
new file mode 100644
index 000000000..777eaab2b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0014.png
new file mode 100644
index 000000000..7f584c1d2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0015.png
new file mode 100644
index 000000000..1309106bd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0016.png
new file mode 100644
index 000000000..54708811d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0017.png
new file mode 100644
index 000000000..7badec902
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0018.png
new file mode 100644
index 000000000..169e0c27f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0019.png
new file mode 100644
index 000000000..5d52f872c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0020.png
new file mode 100644
index 000000000..0e1a30913
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0021.png
new file mode 100644
index 000000000..ae37dfb06
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0022.png
new file mode 100644
index 000000000..14b5ab0fc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0023.png
new file mode 100644
index 000000000..381854784
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0024.png
new file mode 100644
index 000000000..1ab37b1c9
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchin0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0001.png
new file mode 100644
index 000000000..ea1fc3986
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0002.png
new file mode 100644
index 000000000..2dde60adb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0003.png
new file mode 100644
index 000000000..021d7ba06
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0004.png
new file mode 100644
index 000000000..138580b99
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0005.png
new file mode 100644
index 000000000..d530ce175
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0006.png
new file mode 100644
index 000000000..8b2e78482
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0007.png
new file mode 100644
index 000000000..896c630e1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0008.png
new file mode 100644
index 000000000..e6f8d21a3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0009.png
new file mode 100644
index 000000000..ec94972ea
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0010.png
new file mode 100644
index 000000000..e3debca3b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0011.png
new file mode 100644
index 000000000..150d0c2c2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0012.png
new file mode 100644
index 000000000..63bc2af3e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0013.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0014.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0015.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0016.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0017.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0018.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0019.png
new file mode 100644
index 000000000..d0ee2d37f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0020.png
new file mode 100644
index 000000000..01d5bfcb2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0021.png
new file mode 100644
index 000000000..336ac57c3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0022.png
new file mode 100644
index 000000000..72eb25f69
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0023.png
new file mode 100644
index 000000000..6fa2cb0af
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0024.png
new file mode 100644
index 000000000..8b0f03bf4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_pinchout0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0001.png
new file mode 100644
index 000000000..fdeeb1fa3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0002.png
new file mode 100644
index 000000000..3b11a200e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0003.png
new file mode 100644
index 000000000..dd6e1eadb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0004.png
new file mode 100644
index 000000000..6e3d0fa26
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0005.png
new file mode 100644
index 000000000..23b651dd3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0006.png
new file mode 100644
index 000000000..ec0e6f3c7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0007.png
new file mode 100644
index 000000000..4964b5cc7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0008.png
new file mode 100644
index 000000000..6cd93a245
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0009.png
new file mode 100644
index 000000000..3bb106cf4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0010.png
new file mode 100644
index 000000000..319b9b188
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0011.png
new file mode 100644
index 000000000..330533867
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0012.png
new file mode 100644
index 000000000..88ce3c018
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0013.png
new file mode 100644
index 000000000..cd267adaa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0014.png
new file mode 100644
index 000000000..519af9c1b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0015.png
new file mode 100644
index 000000000..83fc3423e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0016.png
new file mode 100644
index 000000000..30e8b240b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0017.png
new file mode 100644
index 000000000..739827087
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0018.png
new file mode 100644
index 000000000..d98c2fb5b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0019.png
new file mode 100644
index 000000000..047e5bebd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0020.png
new file mode 100644
index 000000000..8f26a95e2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0021.png
new file mode 100644
index 000000000..19efdb4ac
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0022.png
new file mode 100644
index 000000000..16466a4e1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0023.png
new file mode 100644
index 000000000..90a674f13
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0024.png
new file mode 100644
index 000000000..1601b4338
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_shake0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0001.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0002.png
new file mode 100644
index 000000000..196f67494
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0003.png
new file mode 100644
index 000000000..763eaafec
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0004.png
new file mode 100644
index 000000000..19b798a46
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0005.png
new file mode 100644
index 000000000..85b444cfa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0006.png
new file mode 100644
index 000000000..832414b89
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0007.png
new file mode 100644
index 000000000..a3f9dd6dd
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0008.png
new file mode 100644
index 000000000..5eb7ed211
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0009.png
new file mode 100644
index 000000000..c870a5fad
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0010.png
new file mode 100644
index 000000000..a750a0230
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0011.png
new file mode 100644
index 000000000..acfa3c121
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0012.png
new file mode 100644
index 000000000..f13b9fe1d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0013.png
new file mode 100644
index 000000000..7a9466346
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0014.png
new file mode 100644
index 000000000..c1ee27c20
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0015.png
new file mode 100644
index 000000000..450888074
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0016.png
new file mode 100644
index 000000000..1023c800d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0017.png
new file mode 100644
index 000000000..be2652f9a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0018.png
new file mode 100644
index 000000000..fc130a338
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0019.png
new file mode 100644
index 000000000..db29ef2cc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0020.png
new file mode 100644
index 000000000..8bd2714df
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0021.png
new file mode 100644
index 000000000..8d4cab9c4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0022.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0023.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0024.png
new file mode 100644
index 000000000..196f67494
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipedown0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0001.png
new file mode 100644
index 000000000..30b13f2c2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0002.png
new file mode 100644
index 000000000..c5c86a2d1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0003.png
new file mode 100644
index 000000000..4f7ea58e0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0004.png
new file mode 100644
index 000000000..4ca59c42a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0005.png
new file mode 100644
index 000000000..df727f66c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0006.png
new file mode 100644
index 000000000..9d068cdba
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0007.png
new file mode 100644
index 000000000..183d27482
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0008.png
new file mode 100644
index 000000000..bd21dfcd0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0009.png
new file mode 100644
index 000000000..5ec92c718
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0010.png
new file mode 100644
index 000000000..5cb194613
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0011.png
new file mode 100644
index 000000000..957bed986
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0012.png
new file mode 100644
index 000000000..aefd11f88
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0013.png
new file mode 100644
index 000000000..789d831c2
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0014.png
new file mode 100644
index 000000000..1733eba70
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0015.png
new file mode 100644
index 000000000..0f84c9121
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0016.png
new file mode 100644
index 000000000..0073e2e01
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0017.png
new file mode 100644
index 000000000..34483780f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0018.png
new file mode 100644
index 000000000..b93793aea
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0019.png
new file mode 100644
index 000000000..641995e82
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0020.png
new file mode 100644
index 000000000..7cd3c91ad
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0021.png
new file mode 100644
index 000000000..cc6fcd582
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0022.png
new file mode 100644
index 000000000..0537efbea
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0023.png
new file mode 100644
index 000000000..ec69020ce
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0024.png
new file mode 100644
index 000000000..6129865ec
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeleft0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0001.png
new file mode 100644
index 000000000..8f757df57
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0002.png
new file mode 100644
index 000000000..343185af8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0003.png
new file mode 100644
index 000000000..255ef6cf5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0004.png
new file mode 100644
index 000000000..747cf600e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0005.png
new file mode 100644
index 000000000..34d505aff
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0006.png
new file mode 100644
index 000000000..9ab1f3e6b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0007.png
new file mode 100644
index 000000000..d97424f94
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0008.png
new file mode 100644
index 000000000..f62558e0a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0009.png
new file mode 100644
index 000000000..5217e1a29
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0010.png
new file mode 100644
index 000000000..e717eedd4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0011.png
new file mode 100644
index 000000000..d8cccb176
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0012.png
new file mode 100644
index 000000000..b5866a4fb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0013.png
new file mode 100644
index 000000000..5777d5e01
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0014.png
new file mode 100644
index 000000000..67311b0b5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0015.png
new file mode 100644
index 000000000..b1a4dfa8e
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0016.png
new file mode 100644
index 000000000..9435c6933
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0017.png
new file mode 100644
index 000000000..6653ada95
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0018.png
new file mode 100644
index 000000000..e2e5ad263
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0019.png
new file mode 100644
index 000000000..361cbeec0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0020.png
new file mode 100644
index 000000000..25d5ed75d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0021.png
new file mode 100644
index 000000000..34445ac58
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0022.png
new file mode 100644
index 000000000..4887e5c19
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0023.png
new file mode 100644
index 000000000..22839c67d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0024.png
new file mode 100644
index 000000000..8f757df57
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swiperight0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0001.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0002.png
new file mode 100644
index 000000000..15eb8101a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0003.png
new file mode 100644
index 000000000..5e914e985
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0004.png
new file mode 100644
index 000000000..067606d99
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0005.png
new file mode 100644
index 000000000..9f30e72f5
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0006.png
new file mode 100644
index 000000000..c53e80c10
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0007.png
new file mode 100644
index 000000000..ee58e233c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0008.png
new file mode 100644
index 000000000..964a01a45
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0009.png
new file mode 100644
index 000000000..964a01a45
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0010.png
new file mode 100644
index 000000000..964a01a45
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0011.png
new file mode 100644
index 000000000..cff9ac554
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0012.png
new file mode 100644
index 000000000..0ff5a2e36
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0013.png
new file mode 100644
index 000000000..d732c80d1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0014.png
new file mode 100644
index 000000000..43a5b8e47
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0015.png
new file mode 100644
index 000000000..784c26be1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0016.png
new file mode 100644
index 000000000..381b5cef1
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0017.png
new file mode 100644
index 000000000..afa69e037
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0018.png
new file mode 100644
index 000000000..86cd903ae
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0019.png
new file mode 100644
index 000000000..66666c734
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0020.png
new file mode 100644
index 000000000..103f9947a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0021.png
new file mode 100644
index 000000000..7982f4d8b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0022.png
new file mode 100644
index 000000000..2506c82cb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0023.png
new file mode 100644
index 000000000..66420e2c3
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0024.png
new file mode 100644
index 000000000..8fc8923c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_swipeup0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0001.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0001.png
new file mode 100644
index 000000000..f0a1cd493
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0001.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0002.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0002.png
new file mode 100644
index 000000000..65d655508
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0002.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0003.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0003.png
new file mode 100644
index 000000000..96afe1a96
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0003.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0004.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0004.png
new file mode 100644
index 000000000..fc78fd9f8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0004.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0005.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0005.png
new file mode 100644
index 000000000..09ed3a3f8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0005.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0006.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0006.png
new file mode 100644
index 000000000..336d46103
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0006.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0007.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0007.png
new file mode 100644
index 000000000..5d657d0cb
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0007.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0008.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0008.png
new file mode 100644
index 000000000..20ee98019
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0008.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0009.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0009.png
new file mode 100644
index 000000000..5e5a464ff
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0009.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0010.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0010.png
new file mode 100644
index 000000000..8819f4721
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0010.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0011.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0011.png
new file mode 100644
index 000000000..ca37f4781
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0011.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0012.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0012.png
new file mode 100644
index 000000000..5a944a695
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0012.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0013.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0013.png
new file mode 100644
index 000000000..87bd76a32
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0013.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0014.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0014.png
new file mode 100644
index 000000000..00c7f17bc
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0014.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0015.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0015.png
new file mode 100644
index 000000000..08d8602c0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0015.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0016.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0016.png
new file mode 100644
index 000000000..1eb8ba8b4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0016.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0017.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0017.png
new file mode 100644
index 000000000..6124f6dd6
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0017.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0018.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0018.png
new file mode 100644
index 000000000..5edebf86f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0018.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0019.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0019.png
new file mode 100644
index 000000000..bd6f40734
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0019.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0020.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0020.png
new file mode 100644
index 000000000..d9af99f8f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0020.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0021.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0021.png
new file mode 100644
index 000000000..8b9f2fb45
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0021.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0022.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0022.png
new file mode 100644
index 000000000..2fa345787
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0022.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0023.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0023.png
new file mode 100644
index 000000000..b0a7aba3a
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0023.png differ
diff --git a/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0024.png b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0024.png
new file mode 100644
index 000000000..58301c7e0
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-nodpi/snowman_tap0024.png differ
diff --git a/dasherdancer/src/main/res/drawable-v21/dasher_ripple.xml b/dasherdancer/src/main/res/drawable-v21/dasher_ripple.xml
new file mode 100644
index 000000000..67e022209
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable-v21/dasher_ripple.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/dasherdancer/src/main/res/drawable-v21/dasher_ripple_small.xml b/dasherdancer/src/main/res/drawable-v21/dasher_ripple_small.xml
new file mode 100644
index 000000000..50ab5debd
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable-v21/dasher_ripple_small.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..2050120fa
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..cae06c682
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character.png
new file mode 100644
index 000000000..c02828653
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character_pressed.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character_pressed.png
new file mode 100644
index 000000000..794c3cb5f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_select_character_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out.png
new file mode 100644
index 000000000..bc5da3542
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out_pressed.png b/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out_pressed.png
new file mode 100644
index 000000000..598b9d0c8
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/btn_x_out_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/elf.png b/dasherdancer/src/main/res/drawable-xhdpi/elf.png
new file mode 100644
index 000000000..bfdff002b
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/elf.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..df82a166d
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_left.png b/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_left.png
new file mode 100644
index 000000000..0724f6e65
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_left.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_right.png b/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_right.png
new file mode 100644
index 000000000..e151aa236
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/icn_arrow_right.png differ
diff --git a/dasherdancer/src/main/res/drawable-xhdpi/img_mint_loading_spinner.png b/dasherdancer/src/main/res/drawable-xhdpi/img_mint_loading_spinner.png
new file mode 100644
index 000000000..96911bff7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xhdpi/img_mint_loading_spinner.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..28738e251
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..b810e7035
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character.png
new file mode 100644
index 000000000..fce126edf
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character_pressed.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character_pressed.png
new file mode 100644
index 000000000..cc6568d2f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_select_character_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out.png
new file mode 100644
index 000000000..e3af85425
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out_pressed.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out_pressed.png
new file mode 100644
index 000000000..d61aa5601
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/btn_x_out_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..91475edc4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/img_mint_loading_spinner.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/img_mint_loading_spinner.png
new file mode 100644
index 000000000..8b1a16985
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/img_mint_loading_spinner.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/reindeer.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/reindeer.png
new file mode 100644
index 000000000..2c58a3006
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/reindeer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/santa.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/santa.png
new file mode 100644
index 000000000..3a841f400
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/santa.png differ
diff --git a/dasherdancer/src/main/res/drawable-xlarge-xhdpi/snowman.png b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/snowman.png
new file mode 100644
index 000000000..c8b6e4d73
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xlarge-xhdpi/snowman.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..ec65de817
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..94c36a676
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character.png
new file mode 100644
index 000000000..5bc4bf761
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character_pressed.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character_pressed.png
new file mode 100644
index 000000000..2a1e05a4c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_select_character_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out.png
new file mode 100644
index 000000000..c0a791bf7
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out_pressed.png b/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out_pressed.png
new file mode 100644
index 000000000..209349e0f
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/btn_x_out_pressed.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/games_cancelbar.png b/dasherdancer/src/main/res/drawable-xxhdpi/games_cancelbar.png
new file mode 100644
index 000000000..06123258c
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/games_cancelbar.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..91475edc4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/img_mint_loading_spinner.png b/dasherdancer/src/main/res/drawable-xxhdpi/img_mint_loading_spinner.png
new file mode 100644
index 000000000..5970d6b81
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/img_mint_loading_spinner.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/reindeer.png b/dasherdancer/src/main/res/drawable-xxhdpi/reindeer.png
new file mode 100644
index 000000000..2c58a3006
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/reindeer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/santa.png b/dasherdancer/src/main/res/drawable-xxhdpi/santa.png
new file mode 100644
index 000000000..3a841f400
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/santa.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxhdpi/snowman.png b/dasherdancer/src/main/res/drawable-xxhdpi/snowman.png
new file mode 100644
index 000000000..c8b6e4d73
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxhdpi/snowman.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer.png b/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer.png
new file mode 100644
index 000000000..ec65de817
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer_p.png b/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer_p.png
new file mode 100644
index 000000000..94c36a676
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/btn_nav_drawer_p.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/ic_launcher.png b/dasherdancer/src/main/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..91475edc4
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/reindeer.png b/dasherdancer/src/main/res/drawable-xxxhdpi/reindeer.png
new file mode 100644
index 000000000..2c58a3006
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/reindeer.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/santa.png b/dasherdancer/src/main/res/drawable-xxxhdpi/santa.png
new file mode 100644
index 000000000..3a841f400
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/santa.png differ
diff --git a/dasherdancer/src/main/res/drawable-xxxhdpi/snowman.png b/dasherdancer/src/main/res/drawable-xxxhdpi/snowman.png
new file mode 100644
index 000000000..c8b6e4d73
Binary files /dev/null and b/dasherdancer/src/main/res/drawable-xxxhdpi/snowman.png differ
diff --git a/dasherdancer/src/main/res/drawable/btn_main_menu.xml b/dasherdancer/src/main/res/drawable/btn_main_menu.xml
new file mode 100644
index 000000000..819fea8c5
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable/btn_main_menu.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/dasherdancer/src/main/res/drawable/btn_select_character_pressable.xml b/dasherdancer/src/main/res/drawable/btn_select_character_pressable.xml
new file mode 100644
index 000000000..ae98f86fb
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable/btn_select_character_pressable.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/drawable/btn_x_out_pressable.xml b/dasherdancer/src/main/res/drawable/btn_x_out_pressable.xml
new file mode 100644
index 000000000..408a9bfad
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable/btn_x_out_pressable.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/drawable/dasher_ripple.xml b/dasherdancer/src/main/res/drawable/dasher_ripple.xml
new file mode 100644
index 000000000..180e6992b
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable/dasher_ripple.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
diff --git a/dasherdancer/src/main/res/drawable/dasher_ripple_small.xml b/dasherdancer/src/main/res/drawable/dasher_ripple_small.xml
new file mode 100644
index 000000000..27adb1e1e
--- /dev/null
+++ b/dasherdancer/src/main/res/drawable/dasher_ripple_small.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
diff --git a/dasherdancer/src/main/res/layout/activity_character.xml b/dasherdancer/src/main/res/layout/activity_character.xml
new file mode 100644
index 000000000..799dc5394
--- /dev/null
+++ b/dasherdancer/src/main/res/layout/activity_character.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/layout/activity_dasher_dancer.xml b/dasherdancer/src/main/res/layout/activity_dasher_dancer.xml
new file mode 100644
index 000000000..c1d01a778
--- /dev/null
+++ b/dasherdancer/src/main/res/layout/activity_dasher_dancer.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/raw/elf_pinchin_ball.mp3 b/dasherdancer/src/main/res/raw/elf_pinchin_ball.mp3
new file mode 100644
index 000000000..244ca66ef
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_pinchin_ball.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_pinchout.mp3 b/dasherdancer/src/main/res/raw/elf_pinchout.mp3
new file mode 100644
index 000000000..d58c50bbf
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_pinchout.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_shake2.mp3 b/dasherdancer/src/main/res/raw/elf_shake2.mp3
new file mode 100644
index 000000000..27702f7de
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_shake2.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_swipedown2.mp3 b/dasherdancer/src/main/res/raw/elf_swipedown2.mp3
new file mode 100644
index 000000000..d8a9ec0da
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_swipedown2.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_swipeleft.mp3 b/dasherdancer/src/main/res/raw/elf_swipeleft.mp3
new file mode 100644
index 000000000..9115583f7
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_swipeleft.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_swiperight.mp3 b/dasherdancer/src/main/res/raw/elf_swiperight.mp3
new file mode 100644
index 000000000..884d129ff
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_swiperight.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_swipeup2.mp3 b/dasherdancer/src/main/res/raw/elf_swipeup2.mp3
new file mode 100644
index 000000000..c7025c9c0
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_swipeup2.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/elf_tap3.mp3 b/dasherdancer/src/main/res/raw/elf_tap3.mp3
new file mode 100644
index 000000000..ccd432bf6
Binary files /dev/null and b/dasherdancer/src/main/res/raw/elf_tap3.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_pinchin.mp3 b/dasherdancer/src/main/res/raw/reindeer_pinchin.mp3
new file mode 100644
index 000000000..22d80458d
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_pinchin.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_pinchout.mp3 b/dasherdancer/src/main/res/raw/reindeer_pinchout.mp3
new file mode 100644
index 000000000..b00062a94
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_pinchout.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_shake.mp3 b/dasherdancer/src/main/res/raw/reindeer_shake.mp3
new file mode 100644
index 000000000..1f8aa3f11
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_shake.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_swipedown.wav b/dasherdancer/src/main/res/raw/reindeer_swipedown.wav
new file mode 100644
index 000000000..348c2bd05
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_swipedown.wav differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_swipeleft.mp3 b/dasherdancer/src/main/res/raw/reindeer_swipeleft.mp3
new file mode 100644
index 000000000..e12dc4a79
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_swipeleft.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_swiperight.mp3 b/dasherdancer/src/main/res/raw/reindeer_swiperight.mp3
new file mode 100644
index 000000000..5c6fcff88
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_swiperight.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_swipeup.mp3 b/dasherdancer/src/main/res/raw/reindeer_swipeup.mp3
new file mode 100644
index 000000000..65e182e9e
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_swipeup.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/reindeer_tap2.mp3 b/dasherdancer/src/main/res/raw/reindeer_tap2.mp3
new file mode 100644
index 000000000..127bed0cd
Binary files /dev/null and b/dasherdancer/src/main/res/raw/reindeer_tap2.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_pinchin.mp3 b/dasherdancer/src/main/res/raw/santa_pinchin.mp3
new file mode 100644
index 000000000..2c6b03fe7
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_pinchin.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_pinchout.mp3 b/dasherdancer/src/main/res/raw/santa_pinchout.mp3
new file mode 100644
index 000000000..50c1fb17b
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_pinchout.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_shake.mp3 b/dasherdancer/src/main/res/raw/santa_shake.mp3
new file mode 100644
index 000000000..ecc949cf6
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_shake.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_swipedown.mp3 b/dasherdancer/src/main/res/raw/santa_swipedown.mp3
new file mode 100644
index 000000000..ea13c5ab6
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_swipedown.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_swipeleft.mp3 b/dasherdancer/src/main/res/raw/santa_swipeleft.mp3
new file mode 100644
index 000000000..c8b38e3c7
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_swipeleft.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_swiperight.mp3 b/dasherdancer/src/main/res/raw/santa_swiperight.mp3
new file mode 100644
index 000000000..6345d8ce5
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_swiperight.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_swipeup.mp3 b/dasherdancer/src/main/res/raw/santa_swipeup.mp3
new file mode 100644
index 000000000..ad5ebc419
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_swipeup.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/santa_tap.mp3 b/dasherdancer/src/main/res/raw/santa_tap.mp3
new file mode 100644
index 000000000..3082b8998
Binary files /dev/null and b/dasherdancer/src/main/res/raw/santa_tap.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_pinchin.mp3 b/dasherdancer/src/main/res/raw/snowman_pinchin.mp3
new file mode 100644
index 000000000..cbb6c96d7
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_pinchin.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_pinchout.mp3 b/dasherdancer/src/main/res/raw/snowman_pinchout.mp3
new file mode 100644
index 000000000..688598176
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_pinchout.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_shake.mp3 b/dasherdancer/src/main/res/raw/snowman_shake.mp3
new file mode 100644
index 000000000..c39704dcb
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_shake.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_swipedown.mp3 b/dasherdancer/src/main/res/raw/snowman_swipedown.mp3
new file mode 100644
index 000000000..5b0b87ea4
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_swipedown.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_swipeleft.mp3 b/dasherdancer/src/main/res/raw/snowman_swipeleft.mp3
new file mode 100644
index 000000000..a57e548ef
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_swipeleft.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_swiperight.mp3 b/dasherdancer/src/main/res/raw/snowman_swiperight.mp3
new file mode 100644
index 000000000..9266e4961
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_swiperight.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_swipeup.mp3 b/dasherdancer/src/main/res/raw/snowman_swipeup.mp3
new file mode 100644
index 000000000..9a542cc20
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_swipeup.mp3 differ
diff --git a/dasherdancer/src/main/res/raw/snowman_tap.mp3 b/dasherdancer/src/main/res/raw/snowman_tap.mp3
new file mode 100644
index 000000000..0d5caae13
Binary files /dev/null and b/dasherdancer/src/main/res/raw/snowman_tap.mp3 differ
diff --git a/dasherdancer/src/main/res/values-hdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-hdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-hdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-mdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-mdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..0e540c993
--- /dev/null
+++ b/dasherdancer/src/main/res/values-mdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 4
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-sw600dp-mdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-sw600dp-mdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-sw600dp-mdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-sw600dp-tvdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-sw600dp-tvdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-sw600dp-tvdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-sw600dp-xhdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-sw600dp-xhdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..54fc80742
--- /dev/null
+++ b/dasherdancer/src/main/res/values-sw600dp-xhdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 1
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-sw600dp/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-sw600dp/dasher_dancer_values.xml
new file mode 100644
index 000000000..54fc80742
--- /dev/null
+++ b/dasherdancer/src/main/res/values-sw600dp/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 1
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-sw720dp/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-sw720dp/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-sw720dp/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-xhdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-xhdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-xhdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-xxhdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-xxhdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..3ca7d661e
--- /dev/null
+++ b/dasherdancer/src/main/res/values-xxhdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 2
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values-xxxhdpi/dasher_dancer_values.xml b/dasherdancer/src/main/res/values-xxxhdpi/dasher_dancer_values.xml
new file mode 100644
index 000000000..54fc80742
--- /dev/null
+++ b/dasherdancer/src/main/res/values-xxxhdpi/dasher_dancer_values.xml
@@ -0,0 +1,6 @@
+
+
+
+ 1
+
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values/colors.xml b/dasherdancer/src/main/res/values/colors.xml
new file mode 100644
index 000000000..c669fc595
--- /dev/null
+++ b/dasherdancer/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+ #FFF9CE1D
+ #FF4FC3F7
+ #FFBA68C8
+ #FF7CB342
+ #FFF5871F
+ #FFB43A2D
+ #FFD0A260
+ #FFeb3f79
+
diff --git a/dasherdancer/src/main/res/values/game_ids.xml b/dasherdancer/src/main/res/values/game_ids.xml
new file mode 100644
index 000000000..2530db809
--- /dev/null
+++ b/dasherdancer/src/main/res/values/game_ids.xml
@@ -0,0 +1,7 @@
+
+
+ CgkIioKF2qwCEAIQGw
+ CgkIioKF2qwCEAIQHA
+ CgkIioKF2qwCEAIQHQ
+ CgkIioKF2qwCEAIQHg
+
\ No newline at end of file
diff --git a/dasherdancer/src/main/res/values/strings.xml b/dasherdancer/src/main/res/values/strings.xml
new file mode 100644
index 000000000..073e5e546
--- /dev/null
+++ b/dasherdancer/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Dasher Dancer
+
diff --git a/dasherdancer/src/main/res/values/strings_analytics.xml b/dasherdancer/src/main/res/values/strings_analytics.xml
new file mode 100644
index 000000000..d7e7ab1a7
--- /dev/null
+++ b/dasherdancer/src/main/res/values/strings_analytics.xml
@@ -0,0 +1,16 @@
+
+
+ Dasher Dancer
+ Dasher Dancer Characters
+ Changed
+ Dasher Dancer Interaction
+ Shake
+ Swipe Left
+ Swipe Right
+ Swipe Up
+ Swipe Down
+ Pinch In
+ Pinch Out
+ Tap
+ Dasher Dancer Character Select
+
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..8c0fb64a8
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..ed9bb705d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jan 12 15:23:35 EST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 000000000..91a7e269e
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..aec99730b
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/res/snowdown.png b/res/snowdown.png
new file mode 100644
index 000000000..d9d8d7eb1
Binary files /dev/null and b/res/snowdown.png differ
diff --git a/res/village.png b/res/village.png
index e0e386ffd..1de95143e 100644
Binary files a/res/village.png and b/res/village.png differ
diff --git a/rocketsleigh/.gitignore b/rocketsleigh/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/rocketsleigh/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/rocketsleigh/build.gradle b/rocketsleigh/build.gradle
new file mode 100644
index 000000000..4c8796701
--- /dev/null
+++ b/rocketsleigh/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 23
+ }
+}
+
+dependencies {
+ compile project(':common')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile rootProject.ext.supportV4
+}
diff --git a/rocketsleigh/proguard-rules.pro b/rocketsleigh/proguard-rules.pro
new file mode 100644
index 000000000..67abb7535
--- /dev/null
+++ b/rocketsleigh/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/rpetit/android_sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/rocketsleigh/src/main/AndroidManifest.xml b/rocketsleigh/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..60988073f
--- /dev/null
+++ b/rocketsleigh/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BackgroundLoadTask.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BackgroundLoadTask.java
new file mode 100644
index 000000000..30c2d7c15
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BackgroundLoadTask.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+
+/**
+ * Created by rpetit on 11/18/14.
+ */
+public class BackgroundLoadTask extends AsyncTask {
+ private Resources mResources;
+ private Bitmap[] mBackgrounds;
+ private Bitmap[] mBackgrounds2;
+ private Bitmap[] mForegrounds;
+ private Bitmap[] mForegrounds2;
+ private Bitmap[] mExitTransitions;
+ private Bitmap[] mEntryTransitions;
+ private int mBackgroundRes;
+ private int mForegroundRes;
+ private int mExitTransitionRes;
+ private int mEntryTransitionRes;
+ private float mScaleX;
+ private float mScaleY;
+ private int mLevel;
+ private int mScreenWidth;
+ private int mScreenHeight;
+
+ public BackgroundLoadTask(
+ Resources resources,
+ int level,
+ int backgroudRes,
+ int exitTransitionRes,
+ int entryTransitionRes,
+ float scaleX,
+ float scaleY,
+ Bitmap[] backgrounds,
+ Bitmap[] backgrounds2,
+ Bitmap[] exitTransitions,
+ Bitmap[] entryTransitions,
+ int screenWidth,
+ int screenHeight)
+ {
+ mResources = resources;
+ mBackgrounds = backgrounds;
+ mBackgrounds2 = backgrounds2;
+ mExitTransitions = exitTransitions;
+ mEntryTransitions = entryTransitions;
+ mBackgroundRes = backgroudRes;
+ mExitTransitionRes = exitTransitionRes;
+ mEntryTransitionRes = entryTransitionRes;
+ mScaleX = scaleX;
+ mScaleY = scaleY;
+ mLevel = level;
+ mScreenWidth = screenWidth;
+ mScreenHeight = screenHeight;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (mResources != null) {
+ // Since exit transitions are for a previous level, use mLevel - 1
+ if ((mExitTransitions != null) && ((mLevel - 1) >= 0) && ((mLevel - 1) < mExitTransitions.length) && (mExitTransitionRes != -1)) {
+ Bitmap bmp = BitmapFactory.decodeResource(mResources, mExitTransitionRes);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth, mScreenHeight, false);
+ synchronized (mExitTransitions) {
+ mExitTransitions[mLevel - 1] = tmp;
+ mExitTransitions.notify();
+ }
+ if (bmp != tmp) {
+ bmp.recycle();
+ }
+ } else {
+ synchronized (mExitTransitions) {
+ mExitTransitions[mLevel - 1] = bmp;
+ mExitTransitions.notify();
+ }
+ }
+ }
+ if (mLevel == 6) {
+ mLevel = 1;
+ }
+ if ((mBackgrounds != null) && (mLevel >= 0) && (mLevel < mBackgrounds.length)) {
+ Bitmap bmp = BitmapFactory.decodeResource(mResources, mBackgroundRes);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, 2 * mScreenWidth, mScreenHeight, false);
+ if (tmp != bmp) {
+ bmp.recycle();
+ bmp = tmp;
+ }
+ }
+ synchronized (mBackgrounds) {
+ createTwoBitmaps(bmp, mBackgrounds, mBackgrounds2, mLevel);
+ mBackgrounds.notify();
+ }
+ bmp.recycle();
+ }
+
+ if ((mEntryTransitions != null) && (mLevel >= 0) && (mLevel < mEntryTransitions.length) && (mEntryTransitionRes != -1)) {
+ Bitmap bmp = BitmapFactory.decodeResource(mResources, mEntryTransitionRes);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth, (int)((float)bmp.getHeight() * mScaleY), false);
+ synchronized (mEntryTransitions) {
+ mEntryTransitions[mLevel] = tmp;
+ mEntryTransitions.notify();
+ }
+ if (bmp != tmp) {
+ bmp.recycle();
+ }
+ } else {
+ synchronized (mEntryTransitions) {
+ mEntryTransitions[mLevel] = bmp;
+ mEntryTransitions.notify();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void createTwoBitmaps(Bitmap src, Bitmap[] bmps1, Bitmap[] bmps2, int level) {
+ int hw = src.getWidth()/2;
+ Bitmap bmp1 = Bitmap.createBitmap(src, 0, 0, hw, src.getHeight());
+ Bitmap bmp2 = Bitmap.createBitmap(src, src.getWidth()/2, 0, src.getWidth() - hw, src.getHeight());
+ bmps1[level] = bmp1;
+ bmps2[level] = bmp2;
+ src.recycle();
+ }
+}
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BitmapLoader.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BitmapLoader.java
new file mode 100644
index 000000000..5a6c9b700
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/BitmapLoader.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.util.Size;
+import android.widget.ImageView;
+
+import java.util.TreeMap;
+
+/**
+ * Created by rpetit on 11/13/14.
+ */
+public class BitmapLoader {
+ private class BitmapLoaderTask extends AsyncTask {
+ private boolean mForSize;
+ private ImageView mView;
+
+ @Override
+ protected Bitmap doInBackground(Integer... params) {
+ Bitmap bmp = null;
+ if (params != null) {
+ for (Integer id : params) {
+ if (mForSize) {
+ Size size = null;
+ synchronized (mBitmapSizes) {
+ size = mBitmapSizes.get(id);
+ }
+ if (size == null) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ bmp = BitmapFactory.decodeResource(mResources, id, opts);
+// size = new Size(bmp.getWidth(), bmp.getHeight());
+ synchronized (mBitmapSizes) {
+ mBitmapSizes.put(id, size);
+ }
+ bmp.recycle();
+ }
+ bmp = null;
+ } else {
+ synchronized (mBitmaps) {
+ bmp = mBitmaps.get(id);
+ }
+ if (bmp == null) {
+ bmp = BitmapFactory.decodeResource(mResources, id);
+ synchronized (mBitmaps) {
+ mBitmaps.put(id, bmp);
+ }
+// Size size = new Size(bmp.getWidth(), bmp.getHeight());
+// mBitmapSizes.put(id, size);
+ }
+ }
+ if (isCancelled()) {
+
+ }
+ }
+ }
+
+ // We only load the bitmap in a view if it's a single bitmap request and not just for size.
+ if (mForSize || (params.length > 1)) {
+ bmp = null;
+ }
+
+ return bmp;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bmp) {
+ if ((mView != null) && (bmp != null)) {
+ // Should check to see if the view is visible yet. If it is,
+ // we may not want to show it.
+ mView.setImageBitmap(bmp);
+ }
+ }
+ }
+
+ private TreeMap mBitmaps;
+ private TreeMap mBitmapSizes;
+ private Resources mResources;
+
+ private static BitmapLoader gLoader;
+
+ public BitmapLoader getInstance(Context context) {
+ if (gLoader == null) {
+ gLoader = new BitmapLoader(context);
+ }
+ return gLoader;
+ }
+
+ private BitmapLoader(Context context) {
+ mResources = context.getResources();
+ mBitmaps = new TreeMap();
+ mBitmapSizes = new TreeMap();
+ }
+
+ public void loadBitmapForView(int resId, ImageView view) {
+ }
+
+ public void preloadBitmap(int resId) {
+ }
+
+ public void preloadBitmaps(int[] ids) {
+ }
+
+ public void preloadBitmapsForSize(int[] ids) {
+ }
+
+ public Size getBitmapSize(int resId) {
+ return null;
+ }
+}
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/EndGameActivity.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/EndGameActivity.java
new file mode 100644
index 000000000..8918b5a98
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/EndGameActivity.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.FragmentActivity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.games.PlayGamesFragment;
+import com.google.android.apps.santatracker.games.SignInListener;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.games.Games;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import java.text.NumberFormat;
+
+
+public class EndGameActivity extends FragmentActivity implements SignInListener {
+
+ private PlayGamesFragment mGamesFragment;
+ private AppInvitesFragment mInviteFragment;
+
+ private View mSignIn;
+ private View mPlayAgain;
+
+ // To handle UI events like KeyDown.
+ private Handler mHandler = new Handler();
+
+ private int[] mAchievements = new int[] {
+ R.string.achievement_hidden_presents,
+ R.string.achievement_rocket_junior_score_10000,
+ R.string.achievement_rocket_intermediate_score_30000,
+ R.string.achievement_rocket_pro_score_50000,
+ R.string.achievement_safe_tapper,
+ R.string.achievement_untouchable
+ };
+
+ private FirebaseAnalytics mMeasurement;
+
+ private static final int REQUEST_ACHIEVEMENTS = 2001;
+ private static final int REQUEST_LEADERBOARD = 2002;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_end_game);
+
+ TextView sv = (TextView)findViewById(R.id.score_text);
+ final long score = getIntent().getLongExtra("score", 0);
+ sv.setText(NumberFormat.getNumberInstance().format(score));
+
+ mSignIn = findViewById(R.id.play_again_gplus);
+
+ // App Invites
+ mInviteFragment = AppInvitesFragment.getInstance(this);
+ findViewById(R.id.invite).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mInviteFragment.sendGameInvite(
+ getString(R.string.rocket), "rocketsleigh", (int) score);
+ }
+ });
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_rocket_endgame));
+
+ if (TvUtil.isTv(this)) {
+ mSignIn.setVisibility(View.GONE);
+ mPlayAgain = findViewById(R.id.play_again);
+ findViewById(R.id.exit).setVisibility(View.GONE);
+ findViewById(R.id.invite).setVisibility(View.GONE);
+ findViewById(R.id.popup_view).setVisibility(View.GONE);
+ } else {
+ mGamesFragment = PlayGamesFragment.getInstance(this, this);
+
+ if (mGamesFragment.isSignedIn()) {
+ mSignIn.setVisibility(View.GONE);
+ updateAchievementsAndLeaderboard();
+ } else {
+ mSignIn.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ releaseResources();
+ }
+
+ private void releaseResources() {
+ View scoreScene = findViewById(R.id.bg_finalscore);
+ if (scoreScene != null) {
+ ((ImageView) scoreScene).setImageDrawable(null);
+ }
+ View snow = findViewById(R.id.bg_img_finalscore_snowground_blue);
+ if (snow != null) {
+ ((ImageView) snow).setImageDrawable(null);
+ }
+ }
+
+ public void onSignIn(View view) {
+ mGamesFragment.beginUserInitiatedSignIn();
+ }
+
+ public void onPlayAgain(View view) {
+ Intent intent = new Intent(this.getApplicationContext(), RocketSleighActivity.class);
+ intent.putExtra("nomovie", true);
+ startActivity(intent);
+ overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
+ finish();
+ }
+
+ public void onLeaderboards(View view) {
+ if (mGamesFragment.isSignedIn()) {
+ Intent intent = Games.Leaderboards.getLeaderboardIntent(
+ mGamesFragment.getGamesApiClient(), getString(R.string.leaderboard_rocket));
+ startActivityForResult(intent, REQUEST_LEADERBOARD);
+ }
+ }
+
+ public void onAchievements(View view) {
+ if (mGamesFragment.isSignedIn()) {
+ Intent intent = Games.Achievements.getAchievementsIntent(
+ mGamesFragment.getGamesApiClient());
+ startActivityForResult(intent, REQUEST_ACHIEVEMENTS);
+ }
+ }
+
+ public void onExit(View view) {
+ finish();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BUTTON_A:
+ //fall through
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ performPlayClick();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ mSignIn.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ mSignIn.setVisibility(View.GONE);
+ updateAchievementsAndLeaderboard();
+
+ MeasurementManager.recordLogin(mMeasurement);
+ }
+
+ private boolean mProcessingPlayClick = false;
+ private void performPlayClick() {
+ if (!mProcessingPlayClick) {
+ mProcessingPlayClick = true;
+ mPlayAgain.setPressed(true);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mProcessingPlayClick = false;
+ mPlayAgain.setPressed(false);
+ mPlayAgain.performClick();
+ }
+ }, 300);
+ }
+ }
+
+ private void updateAchievementsAndLeaderboard() {
+ if (mGamesFragment.isSignedIn()) {
+ GoogleApiClient apiClient = mGamesFragment.getGamesApiClient();
+ Bundle bundle = getIntent().getExtras();
+
+ for (int id : mAchievements) {
+ String achievementStr = getString(id);
+ if (bundle.containsKey(achievementStr)) {
+ Games.Achievements.unlock(mGamesFragment.getGamesApiClient(), achievementStr);
+ MeasurementManager.recordAchievement(mMeasurement,
+ achievementStr,
+ getString(R.string.analytics_screen_rocket));
+ }
+ }
+
+ Long score = bundle.getLong("score");
+ Games.Leaderboards.submitScore(apiClient,
+ getString(R.string.leaderboard_rocket), score);
+ MeasurementManager.recordGameScore(mMeasurement, score, null,
+ getString(R.string.analytics_screen_rocket));
+ }
+ }
+}
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/ObstacleLoadTask.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/ObstacleLoadTask.java
new file mode 100644
index 000000000..bc0b3285d
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/ObstacleLoadTask.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+
+import java.util.ArrayList;
+import java.util.TreeMap;
+
+/**
+ * Created by rpetit on 11/18/14.
+ */
+public class ObstacleLoadTask extends AsyncTask {
+ private TreeMap mMap;
+ private ArrayList mList;
+ private int mIndex;
+ private Resources mResources;
+ private int[] mResourceIds;
+ private int mStride;
+ private float mScaleX;
+ private float mScaleY;
+
+ public ObstacleLoadTask(Resources resources,
+ int[] resourceIds,
+ TreeMap map,
+ ArrayList list,
+ int index,
+ int stride,
+ float scaleX,
+ float scaleY)
+ {
+ mResources = resources;
+ mResourceIds = resourceIds;
+ mMap = map;
+ mList = list;
+ mIndex = index;
+ mStride = stride;
+ mScaleX = scaleX;
+ mScaleY = scaleY;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ for (int i = mIndex; i < (mIndex + 20); i++) {
+ if (isCancelled()) {
+ break;
+ }
+ if (i < mList.size()) {
+ int obstacle = mList.get(i);
+ for (int j = (obstacle * mStride); j < ((obstacle + 1) * mStride); j++) {
+ // Check just in case something is wonky
+ if (j < mResourceIds.length) {
+ int id = mResourceIds[j];
+ if (id != -1) {
+ // Only need to load it once...
+ if (!mMap.containsKey(id)) {
+ Bitmap bmp = BitmapFactory.decodeResource(mResources, id);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, (int)((float)bmp.getWidth() * mScaleX), (int)((float)bmp.getHeight() * mScaleY), false);
+ if (tmp != bmp) {
+ bmp.recycle();
+ }
+ synchronized (mMap) {
+ mMap.put(id, tmp);
+ mMap.notify();
+ }
+ } else {
+ synchronized (mMap) {
+ mMap.put(id, bmp);
+ mMap.notify();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/RocketSleighActivity.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/RocketSleighActivity.java
new file mode 100644
index 000000000..4f13f25c2
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/RocketSleighActivity.java
@@ -0,0 +1,2415 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.support.v4.app.FragmentActivity;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.AlphaAnimation;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import java.io.IOException;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
+
+public class RocketSleighActivity extends FragmentActivity
+ implements View.OnTouchListener,
+ GestureDetector.OnGestureListener,
+ GestureDetector.OnDoubleTapListener,
+ OnClickListener,
+ SoundPool.OnLoadCompleteListener,
+ MediaPlayer.OnCompletionListener {
+
+ private ImageView mElf;
+ private ImageView mThrust;
+ private Bitmap mElfBitmap;
+ private Bitmap mBurnBitmap;
+ private Bitmap mThrustBitmap;
+ private Bitmap mSmokeBitmpap;
+ private Bitmap mCurrentTrailBitmap;
+ private LinearLayout mElfLayout;
+ private int mElfState = 0; // 0 - 100% ... 3 - 25% 4 - Parachute elf.
+ private boolean mElfIsHit = false;
+ private long mElfHitTime = 0;
+ private float mElfPosX = 100; // Pixels from the left edge
+ private float mElfPosY = 200; // Pixels from the top edge
+ private float mElfVelX = 0.3f; // Horizontal speed.
+ private float mElfVelY = 0.0f; // Vertical speed.
+ private float mElfAccelY = 0.0f; // Acceleration due to thrust if user is touching screen.
+ private float mThrustAccelY; // Vertical acceleration in pixel velocity per second of thrust.
+ private float mGravityAccelY; // Vertical acceleration due to gravity in pixel velocity per second.
+ private long mLastTime;
+ private float mElfScale;
+
+ private CountDownTimer mCountDownTimer;
+
+ // Achievments
+ private boolean mHit = false;
+ private boolean mHitLevel = false;
+ private boolean mCleanLevel = false;
+ private boolean mPresentBonus = false;
+
+ private boolean mRainingPresents = false;
+ private ImageView mPlus100;
+ private ImageView mPlus500;
+ private AlphaAnimation m100Anim;
+ private AlphaAnimation m500Anim;
+
+ private LinearLayout mObstacleLayout;
+ private HorizontalScrollView mObstacleScroll;
+ private int mSlotWidth;
+ // This is the width of an ornament "slot". Obstacles can span multiple slots.
+ private Random mRandom;
+ private int mLastTopHeight = 0;
+ private int mLastBottomHeight = 0;
+
+ private LinearLayout mBackgroundLayout;
+ private HorizontalScrollView mBackgroundScroll;
+ private LinearLayout mForegroundLayout;
+ private HorizontalScrollView mForegroundScroll;
+ private float mScaleY = 1.0f;
+ private float mScaleX = 1.0f;
+
+ private TextView mScoreText;
+ private String mScoreLabel;
+ private ImageView mPlayPauseButton;
+
+ private int mScreenHeight;
+ private int mScreenWidth;
+
+ private View mControlView;
+ private GestureDetector mGestureDetector;
+ private MotionEvent mDownEvent;
+
+ private TextView mCountdown;
+
+ private boolean mIsTv = true;
+ private boolean mIsPlaying = false;
+ private boolean mMoviePlaying = false;
+ private boolean mCountdownStarted = false;
+ private int mLevel = 0; // There are six levels.
+ private long mScore = 0;
+ private int mPresentCount = 0; // 5 in a row gets a bonus...
+ private int mBackgroundCount = 0; // 5 copies of backgrounds per level
+ private int mTransitionImagesCount = 0; // Some level transitions have transition images.
+
+ private LayoutInflater mInflater;
+
+ private long mLastFrameTime = 0;
+
+ private Vibrator mVibrator;
+
+ private Handler mHandler;
+
+ private VideoView mIntroVideo;
+ private View mIntroControl;
+ private MediaPlayer mBackgroundPlayer;
+
+ // For sound effects
+ private SoundPool mSoundPool;
+ private int mCrashSound1;
+ private int mCrashSound2;
+ private int mCrashSound3;
+ private int mGameOverSound;
+ private int mJetThrustSound;
+ private int mLevelUpSound;
+ private int mScoreBigSound;
+ private int mScoreSmallSound;
+ private int mJetThrustStream;
+
+ private View mBigPlayButtonLayout;
+ private ImageButton mBigPlayButton;
+
+ private ImageView mExit;
+
+ private FirebaseAnalytics mMeasurement;
+ private AppInvitesFragment mInvitesFragment;
+
+ private static final String LOG_TAG = RocketSleighActivity.class.getSimpleName();
+ private static final int SLOTS_PER_SCREEN = 10;
+
+ private static final int[] BACKGROUNDS = {
+ R.drawable.bg_jet_pack_1,
+ R.drawable.bg_jet_pack_2,
+ R.drawable.bg_jet_pack_3,
+ R.drawable.bg_jet_pack_4,
+ R.drawable.bg_jet_pack_5,
+ R.drawable.bg_jet_pack_6
+ };
+
+ private Bitmap[] mBackgrounds;
+ private Bitmap[] mBackgrounds2;
+
+ private static final int[] FOREGROUNDS = {
+ R.drawable.img_snow_ground_tiles,
+ R.drawable.img_snow_ground_tiles,
+ R.drawable.img_snow_ground_tiles,
+ R.drawable.img_snow_ground_tiles,
+ -1,
+ -1
+ };
+
+ private static final int[] EXIT_TRANSITIONS = {
+ -1,
+ -1,
+ -1,
+ R.drawable.bg_transition_2,
+ -1,
+ R.drawable.bg_transition_4,
+ };
+
+ private Bitmap[] mExitTransitions;
+
+ private static final int[] ENTRY_TRANSITIONS = {
+ -1,
+ -1,
+ R.drawable.bg_transition_1,
+ -1,
+ R.drawable.bg_transition_3,
+ -1
+ };
+
+ private Bitmap[] mEntryTransitions;
+
+ private static final int[] ELF_IMAGES = {
+ R.drawable.img_jetelf_100,
+ R.drawable.img_jetelf_75,
+ R.drawable.img_jetelf_50,
+ R.drawable.img_jetelf_25,
+ R.drawable.img_jetelf_0
+ };
+
+ private Bitmap[] mElfImages;
+
+ private static final int[] ELF_HIT_IMAGES = {
+ R.drawable.img_jetelf_100_hit,
+ R.drawable.img_jetelf_75_hit,
+ R.drawable.img_jetelf_50_hit,
+ R.drawable.img_jetelf_25_hit,
+ };
+
+ private Bitmap[] mElfHitImages;
+
+ private static final int[] ELF_BURN_IMAGES = {
+ R.drawable.img_jet_burn_100,
+ R.drawable.img_jet_burn_75,
+ R.drawable.img_jet_burn_50,
+ R.drawable.img_jet_burn_25
+ };
+
+ private Bitmap[] mElfBurnImages;
+
+ private static final int[] ELF_THRUST_IMAGES = {
+ R.drawable.img_jet_thrust_100,
+ R.drawable.img_jet_thrust_75,
+ R.drawable.img_jet_thrust_50,
+ R.drawable.img_jet_thrust_25
+ };
+
+ private Bitmap[] mElfThrustImages;
+
+ private static final int[] ELF_SMOKE_IMAGES = {
+ R.drawable.img_jet_smoke_100_hit,
+ R.drawable.img_jet_smoke_75_hit,
+ R.drawable.img_jet_smoke_50_hit,
+ R.drawable.img_jet_smoke_25_hit
+ };
+
+ private Bitmap[] mElfSmokeImages;
+
+ private static final int[] GIFT_BOXES = {
+ R.drawable.img_gift_blue_jp,
+ R.drawable.img_gift_green_jp,
+ R.drawable.img_gift_yellow_jp,
+ R.drawable.img_gift_purple_jp,
+ R.drawable.img_gift_red_jp
+ };
+
+ private Bitmap[] mGiftBoxes;
+
+ // Top, Bottom, Background
+ private static final int[] WOOD_OBSTACLES = {
+ -1, R.drawable.img_pine_1_bottom, R.drawable.img_pine_0,
+ -1, R.drawable.img_pine_2_bottom, R.drawable.img_pine_0,
+ -1, R.drawable.img_pine_3_bottom, R.drawable.img_pine_0,
+ -1, R.drawable.img_pine_4_bottom, R.drawable.img_pine_0,
+ R.drawable.img_pine_1_top, -1, R.drawable.img_pine_0,
+ R.drawable.img_pine_2_top, -1, R.drawable.img_pine_0,
+ R.drawable.img_pine_3_top, -1, R.drawable.img_pine_0,
+ R.drawable.img_pine_4_top, -1, R.drawable.img_pine_0,
+ -1, R.drawable.img_birch_1_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_birch_2_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_birch_3_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_birch_4_bottom, R.drawable.img_birch_0,
+ R.drawable.img_birch_1_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_birch_2_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_birch_3_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_birch_4_top, -1, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_1_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_2_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_3_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_4_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_5_bottom, R.drawable.img_birch_0,
+ -1, R.drawable.img_tree_6_bottom, R.drawable.img_birch_0,
+ R.drawable.img_tree_1_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_tree_2_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_tree_3_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_tree_4_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_tree_5_top, -1, R.drawable.img_birch_0,
+ R.drawable.img_tree_6_top, -1, R.drawable.img_birch_0,
+ -1, R.drawable.img_owl, -1,
+ -1, R.drawable.img_log_elf, -1,
+ -1, R.drawable.img_bear_big,
+ -1, R.drawable.img_bear_little
+ };
+
+ private TreeMap mWoodObstacles;
+ private ArrayList mWoodObstacleList;
+ private int mWoodObstacleIndex = 0;
+
+ // Top and bottom, no backgrounds
+ private static final int[] CAVE_OBSTACLES = {
+ R.drawable.img_icicle_small_3, -1,
+ R.drawable.img_icicle_small_4, -1,
+ R.drawable.img_icicle_med_3, -1,
+ R.drawable.img_icicle_med_4, -1,
+ R.drawable.img_icicle_lrg_2, -1,
+ -1, R.drawable.img_icicle_small_1,
+ -1, R.drawable.img_icicle_small_2,
+ -1, R.drawable.img_icicle_med_1,
+ -1, R.drawable.img_icicle_med_2,
+ -1, R.drawable.img_icicle_lrg_1,
+ R.drawable.img_icicle_small_3, R.drawable.img_icicle_small_1,
+ R.drawable.img_icicle_small_3, R.drawable.img_icicle_small_2,
+ R.drawable.img_icicle_small_4, R.drawable.img_icicle_small_1,
+ R.drawable.img_icicle_small_4, R.drawable.img_icicle_small_2,
+ R.drawable.img_2_bats, -1,
+ R.drawable.img_3_bats, -1,
+ R.drawable.img_4_bats, -1,
+ R.drawable.img_5_bats, -1,
+ -1, R.drawable.img_yeti,
+ -1, R.drawable.img_mammoth,
+ -1, R.drawable.img_snow_kiss,
+ -1, R.drawable.img_snowman
+ };
+
+ private TreeMap mCaveObstacles;
+ private ArrayList mCaveObstacleList;
+ private int mCaveObstacleIndex = 0;
+
+ private final static int[] FACTORY_OBSTACLES = {
+ R.drawable.img_icecream_drop, R.drawable.img_icecream_0,
+ R.drawable.img_icecream_drop, R.drawable.img_icecream_1,
+ R.drawable.img_mint_drop_top, R.drawable.img_mint_drop_bottom,
+ R.drawable.img_mint_stack_top, R.drawable.img_mint_stack_bottom,
+ -1, R.drawable.img_candy_cane_0,
+ R.drawable.img_candy_cane_1, -1,
+ -1, R.drawable.img_lollipops,
+ -1, R.drawable.img_choco_fountn,
+ -1, R.drawable.img_candy_buttons,
+ -1, R.drawable.img_mint_gondola,
+ -1, R.drawable.img_candy_cane_0,
+ R.drawable.img_candy_cane_1, -1,
+ -1, R.drawable.img_lollipops,
+ -1, R.drawable.img_choco_fountn,
+ -1, R.drawable.img_candy_buttons,
+ -1, R.drawable.img_mint_gondola,
+ -1, R.drawable.img_candy_cane_0,
+ R.drawable.img_candy_cane_1, -1,
+ -1, R.drawable.img_lollipops,
+ -1, R.drawable.img_choco_fountn,
+ -1, R.drawable.img_candy_buttons,
+ -1, R.drawable.img_mint_gondola
+ };
+
+ private TreeMap mFactoryObstacles;
+ private ArrayList mFactoryObstacleList;
+ private int mFactoryObstacleIndex = 0;
+
+ private Runnable mGameLoop = new Runnable() {
+ @Override
+ public void run() {
+ processFrame();
+ }
+ };
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Log.d(LOG_TAG, "onCreate() : " + savedInstanceState);
+
+ setContentView(R.layout.activity_jet_pack_elf);
+
+ // App Invites
+ mInvitesFragment = AppInvitesFragment.getInstance(this);
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_rocket));
+
+ // [ANALYTICS SCREEN]: Rocket Sleigh
+ AnalyticsManager.initializeAnalyticsTracker(this);
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_rocket);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ ImmersiveModeHelper.setImmersiveSticky(getWindow());
+ ImmersiveModeHelper.installSystemUiVisibilityChangeListener(getWindow());
+ }
+
+ mIntroVideo = (VideoView) findViewById(R.id.intro_view);
+ mIntroControl = findViewById(R.id.intro_control_view);
+ if (savedInstanceState == null) {
+ String path = "android.resource://" + getPackageName() + "/" + R.raw.jp_background;
+ mBackgroundPlayer = new MediaPlayer();
+ try {
+ mBackgroundPlayer.setDataSource(this, Uri.parse(path));
+ mBackgroundPlayer.setLooping(true);
+ mBackgroundPlayer.prepare();
+ mBackgroundPlayer.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ boolean nomovie = false;
+ if (getIntent().getBooleanExtra("nomovie", false)) {
+ nomovie = true;
+ } else if (Build.MANUFACTURER.toUpperCase().contains("SAMSUNG")) {
+// nomovie = true;
+ }
+ if (!nomovie) {
+ mIntroControl.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ endIntro();
+ }
+ });
+ path = "android.resource://" + getPackageName() + "/" + R.raw.intro_wipe;
+ mIntroVideo.setVideoURI(Uri.parse(path));
+ mIntroVideo.setOnCompletionListener(this);
+ mIntroVideo.start();
+ mMoviePlaying = true;
+ } else {
+ mIntroControl.setOnClickListener(null);
+ mIntroControl.setVisibility(View.GONE);
+ mIntroVideo.setVisibility(View.GONE);
+ }
+ } else {
+ mIntroControl.setOnClickListener(null);
+ mIntroControl.setVisibility(View.GONE);
+ mIntroVideo.setVisibility(View.GONE);
+ }
+
+ mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); // For hit indication.
+
+ mHandler = new Handler(); // Get the main UI handler for posting update events
+
+ DisplayMetrics dm = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(dm);
+
+ Log.d(LOG_TAG, "Width: " + dm.widthPixels + " Height: " + dm.heightPixels + " Density: "
+ + dm.density);
+
+ mScreenHeight = dm.heightPixels;
+ mScreenWidth = dm.widthPixels;
+ mSlotWidth = mScreenWidth / SLOTS_PER_SCREEN;
+
+ // Setup the random number generator
+ mRandom = new Random();
+ mRandom.setSeed(
+ System.currentTimeMillis()); // This is ok. We are not looking for cryptographically secure random here!
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ // Setup the background/foreground
+ mBackgroundLayout = (LinearLayout) findViewById(R.id.background_layout);
+ mBackgroundScroll = (HorizontalScrollView) findViewById(R.id.background_scroll);
+ mForegroundLayout = (LinearLayout) findViewById(R.id.foreground_layout);
+ mForegroundScroll = (HorizontalScrollView) findViewById(R.id.foreground_scroll);
+
+ mBackgrounds = new Bitmap[6];
+ mBackgrounds2 = new Bitmap[6];
+ mExitTransitions = new Bitmap[6];
+ mEntryTransitions = new Bitmap[6];
+
+ // Need to vertically scale background to fit the screen. Checkthe image size
+ // compared to screen size and scale appropriately. We will also use the matrix to translate
+ // as we move through the level.
+ Bitmap bmp = BitmapFactory.decodeResource(getResources(), BACKGROUNDS[0]);
+ Log.d(LOG_TAG, "Bitmap Width: " + bmp.getWidth() + " Height: " + bmp.getHeight()
+ + " Screen Width: " + dm.widthPixels + " Height: " + dm.heightPixels);
+ mScaleY = (float) dm.heightPixels / (float) bmp.getHeight();
+ mScaleX = (float) (dm.widthPixels * 2) / (float) bmp
+ .getWidth(); // Ensure that a single bitmap is 2 screens worth of time. (Stock xxhdpi image is 3840x1080)
+
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth * 2, mScreenHeight, false);
+ if (tmp != bmp) {
+ bmp.recycle();
+ bmp = tmp;
+ }
+ }
+ BackgroundLoadTask.createTwoBitmaps(bmp, mBackgrounds, mBackgrounds2, 0);
+
+ // Load the initial background view
+ addNextImages(0);
+ addNextImages(0);
+
+ mWoodObstacles = new TreeMap();
+ mWoodObstacleList = new ArrayList();
+ mWoodObstacleIndex = 0;
+ // We need the bitmaps, so we do pre-load here synchronously.
+ initObstaclesAndPreLoad(WOOD_OBSTACLES, 3, mWoodObstacles, mWoodObstacleList);
+
+ mCaveObstacles = new TreeMap();
+ mCaveObstacleList = new ArrayList();
+ mCaveObstacleIndex = 0;
+ initObstacles(CAVE_OBSTACLES, 2, mCaveObstacleList);
+
+ mFactoryObstacles = new TreeMap();
+ mFactoryObstacleList = new ArrayList();
+ mFactoryObstacleIndex = 0;
+ initObstacles(FACTORY_OBSTACLES, 2, mFactoryObstacleList);
+
+ // Setup the elf
+ mElf = (ImageView) findViewById(R.id.elf_image);
+ mThrust = (ImageView) findViewById(R.id.thrust_image);
+ mElfLayout = (LinearLayout) findViewById(R.id.elf_container);
+ loadElfImages();
+ updateElf(false);
+ // Elf should be the same height relative to the height of the screen on any platform.
+ Matrix scaleMatrix = new Matrix();
+ mElfScale = ((float) dm.heightPixels * 0.123f) / (float) mElfBitmap
+ .getHeight(); // On a 1920x1080 xxhdpi screen, this makes the elf 133 pixels which is the height of the drawable.
+ scaleMatrix.preScale(mElfScale, mElfScale);
+ mElf.setImageMatrix(scaleMatrix);
+ mThrust.setImageMatrix(scaleMatrix);
+ mElfPosX = (dm.widthPixels * 15) / 100; // 15% Into the screen
+ mElfPosY = (dm.heightPixels - ((float) mElfBitmap.getHeight() * mElfScale))
+ / 2; // About 1/2 way down.
+ mElfVelX = (float) dm.widthPixels
+ / 3000.0f; // We start at 3 seconds for a full screen to scroll.
+ mGravityAccelY = (float) (2 * dm.heightPixels) / (float) Math.pow((1.2 * 1000.0),
+ 2.0); // a = 2*d/t^2 Where d = height in pixels and t = 1.2 seconds
+ mThrustAccelY = (float) (2 * dm.heightPixels) / (float) Math.pow((0.7 * 1000.0),
+ 2.0); // a = 2*d/t^2 Where d = height in pixels and t = 0.7 seconds
+
+ // Setup the control view
+ mControlView = findViewById(R.id.control_view);
+ mGestureDetector = new GestureDetector(this, this);
+ mGestureDetector.setIsLongpressEnabled(true);
+ mGestureDetector.setOnDoubleTapListener(this);
+
+ mScoreLabel = getString(R.string.score);
+ mScoreText = (TextView) findViewById(R.id.score_text);
+ mScoreText.setText("0");
+
+ mPlayPauseButton = (ImageView) findViewById(R.id.play_pause_button);
+ mExit = (ImageView) findViewById(R.id.exit);
+
+ // Is Tv?
+ mIsTv = TvUtil.isTv(this);
+ if (mIsTv) {
+ mScoreText.setText(mScoreLabel + ": 0");
+ mPlayPauseButton.setVisibility(View.GONE);
+ mExit.setVisibility(View.GONE);
+ // move scoreLayout position to the Top-Right corner.
+ View scoreLayout = findViewById(R.id.score_layout);
+ RelativeLayout.LayoutParams params
+ = (RelativeLayout.LayoutParams) scoreLayout.getLayoutParams();
+ params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+
+ final int marginTop
+ = getResources().getDimensionPixelOffset(R.dimen.overscan_margin_top);
+ final int marginLeft
+ = getResources().getDimensionPixelOffset(R.dimen.overscan_margin_left);
+
+ params.setMargins(marginLeft, marginTop, 0, 0);
+ scoreLayout.setLayoutParams(params);
+ scoreLayout.setBackground(null);
+ scoreLayout.findViewById(R.id.score_text_seperator).setVisibility(View.GONE);
+ } else {
+ mPlayPauseButton.setEnabled(false);
+ mPlayPauseButton.setOnClickListener(this);
+ mExit.setOnClickListener(this);
+ }
+
+ mBigPlayButtonLayout = findViewById(R.id.big_play_button_layout);
+ mBigPlayButtonLayout.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // No interaction with the screen below this one.
+ return true;
+ }
+ });
+ mBigPlayButton = (ImageButton) findViewById(R.id.big_play_button);
+ mBigPlayButton.setOnClickListener(this);
+
+ // For showing points when getting presents.
+ mPlus100 = (ImageView) findViewById(R.id.plus_100);
+ m100Anim = new AlphaAnimation(1.0f, 0.0f);
+ m100Anim.setDuration(1000);
+ m100Anim.setFillBefore(true);
+ m100Anim.setFillAfter(true);
+ mPlus500 = (ImageView) findViewById(R.id.plus_500);
+ m500Anim = new AlphaAnimation(1.0f, 0.0f);
+ m500Anim.setDuration(1000);
+ m500Anim.setFillBefore(true);
+ m500Anim.setFillAfter(true);
+
+ // Get the obstacle layouts ready. No obstacles on the first screen of a level.
+ // Prime with a screen full of obstacles.
+ mObstacleLayout = (LinearLayout) findViewById(R.id.obstacles_layout);
+ mObstacleScroll = (HorizontalScrollView) findViewById(R.id.obstacles_scroll);
+
+ // Initialize the present bitmaps. These are used repeatedly so we keep them loaded.
+ mGiftBoxes = new Bitmap[GIFT_BOXES.length];
+ for (int i = 0; i < GIFT_BOXES.length; i++) {
+ mGiftBoxes[i] = BitmapFactory.decodeResource(getResources(), GIFT_BOXES[i]);
+ }
+
+ // Add starting obstacles. First screen has presents. Next 3 get obstacles.
+ addFirstScreenPresents();
+// addFinalPresentRun(); // This adds 2 screens of presents
+// addNextObstacles(0, 1);
+ addNextObstacles(0, 3);
+
+ // Setup the sound pool
+ mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
+ mSoundPool.setOnLoadCompleteListener(this);
+ mCrashSound1 = mSoundPool.load(this, R.raw.jp_crash_1, 1);
+ mCrashSound2 = mSoundPool.load(this, R.raw.jp_crash_2, 1);
+ mCrashSound3 = mSoundPool.load(this, R.raw.jp_crash_3, 1);
+ mGameOverSound = mSoundPool.load(this, R.raw.jp_game_over, 1);
+ mJetThrustSound = mSoundPool.load(this, R.raw.jp_jet_thrust, 1);
+ mLevelUpSound = mSoundPool.load(this, R.raw.jp_level_up, 1);
+ mScoreBigSound = mSoundPool.load(this, R.raw.jp_score_big, 1);
+ mScoreSmallSound = mSoundPool.load(this, R.raw.jp_score_small, 1);
+ mJetThrustStream = 0;
+
+ if (!mMoviePlaying) {
+ doCountdown();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ Log.d(LOG_TAG, "onResume()");
+ }
+
+ @Override
+ public void onPause() {
+ Log.d(LOG_TAG, "onPause()");
+
+ if (mMoviePlaying) {
+ if (mIntroVideo != null) {
+ // We are only here if home or lock is pressed or another app (phone)
+ // interrupts. We just go to the pause screen and start the game when
+ // we come back.
+ mIntroVideo.stopPlayback();
+ mIntroVideo.setVisibility(View.GONE);
+ mIntroControl.setOnClickListener(null);
+ mIntroControl.setVisibility(View.GONE);
+ }
+ mMoviePlaying = false;
+ mIsPlaying = true; // this will make pause() show the pause button.
+ } else if (mCountdownStarted) {
+ mCountdown.setVisibility(View.GONE);
+ mCountDownTimer.cancel();
+ mCountdownStarted = false;
+ mIsPlaying = true; // this will make pause() show the pause button.
+ }
+ pause();
+
+ super.onPause();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mInvitesFragment.getInvite(new AppInvitesFragment.GetInvitationCallback() {
+ @Override
+ public void onInvitation(String invitationId, String deepLink) {
+ Log.d(LOG_TAG, "onInvitation:" + deepLink);
+ }
+ }, false);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(LOG_TAG, "onDestroy()");
+ releaseResources();
+ }
+
+ private void releaseResources() {
+ if (mSoundPool != null) {
+ if (mBackgroundPlayer != null) {
+ mBackgroundPlayer.stop();
+ mBackgroundPlayer.release();
+ mBackgroundPlayer = null;
+ }
+ if (mJetThrustStream > 0) {
+ mSoundPool.stop(mJetThrustStream);
+ mJetThrustStream = 0;
+ }
+ mSoundPool.unload(mCrashSound1);
+ mSoundPool.unload(mCrashSound2);
+ mSoundPool.unload(mCrashSound3);
+ mSoundPool.unload(mGameOverSound);
+ mSoundPool.unload(mJetThrustSound);
+ mSoundPool.unload(mLevelUpSound);
+ mSoundPool.unload(mScoreBigSound);
+ mSoundPool.unload(mScoreSmallSound);
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+
+ // recylce big bitmaps as soon as possible.
+ releaseBitmapArray(mBackgrounds);
+ releaseBitmapArray(mBackgrounds2);
+ releaseBitmapArray(mEntryTransitions);
+ releaseBitmapArray(mExitTransitions);
+ releaseBitmapArray(mGiftBoxes);
+ releaseBitmapArray(mElfBurnImages);
+ releaseBitmapArray(mElfImages);
+ releaseBitmapArray(mElfThrustImages);
+ releaseBitmapArray(mElfHitImages);
+ releaseBitmapArray(mElfSmokeImages);
+
+ releaseIntegerBitmapMap(mWoodObstacles);
+ releaseIntegerBitmapMap(mCaveObstacles);
+ releaseIntegerBitmapMap(mFactoryObstacles);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ // We are eating the config changes so that we don't get destroyed/recreated and again
+ // destroyed/recreated when the lock button is pressed!
+ Log.e(LOG_TAG, "Config change: " + config);
+ super.onConfigurationChanged(config);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mMoviePlaying) {
+ if (mIntroVideo != null) {
+ mIntroVideo.stopPlayback();
+ mIntroVideo.setVisibility(View.GONE);
+ mIntroControl.setOnClickListener(null);
+ mIntroControl.setVisibility(View.GONE);
+ }
+ mMoviePlaying = false;
+ super.onBackPressed();
+ } else if (mCountdownStarted) {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ mCountDownTimer = null;
+ }
+ mCountdownStarted = false;
+ super.onBackPressed();
+ } else {
+ if (mIsPlaying) {
+ pause();
+ } else if (mIsTv) {
+ finish();
+ } else {
+ play();
+ }
+ }
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ //fall through
+ case KeyEvent.KEYCODE_BUTTON_A:
+ if (mIsPlaying) {
+ mElfAccelY = mThrustAccelY;
+ if (!mElfIsHit) {
+ updateElfThrust(1);
+ }
+ mJetThrustStream = mSoundPool.play(mJetThrustSound, 1.0f, 1.0f, 1, -1, 1.0f);
+ } else if (!mCountdownStarted && !mMoviePlaying){
+ //game is paused. resume it.
+ mBigPlayButton.setPressed(true);
+ }
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ //fall through
+ case KeyEvent.KEYCODE_BUTTON_A:
+ if (mIsPlaying) {
+ mElfAccelY = 0.0f;
+ if (!mElfIsHit) {
+ updateElfThrust(0);
+ }
+ if (mJetThrustStream > 0) {
+ mSoundPool.stop(mJetThrustStream);
+ mJetThrustStream = 0;
+ }
+ } else if (mMoviePlaying) {
+ endIntro();
+ } else if (mBigPlayButton.isPressed()){
+ mBigPlayButton.setPressed(false);
+ mBigPlayButton.performClick();
+ }
+ return true;
+ case KeyEvent.KEYCODE_BUTTON_B:
+ onBackPressed();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mDownEvent = event;
+ mElfAccelY = mThrustAccelY;
+ if (!mElfIsHit) {
+ updateElfThrust(1);
+ }
+ mJetThrustStream = mSoundPool.play(mJetThrustSound, 1.0f, 1.0f, 1, -1, 1.0f);
+ } else if ((event.getActionMasked() == MotionEvent.ACTION_UP) || (event.getActionMasked()
+ == MotionEvent.ACTION_CANCEL)) {
+ mDownEvent = null;
+ mElfAccelY = 0.0f;
+ if (!mElfIsHit) {
+ updateElfThrust(0);
+ }
+ if (mJetThrustStream > 0) {
+ mSoundPool.stop(mJetThrustStream);
+ mJetThrustStream = 0;
+ }
+ }
+
+// return mGestureDetector.onTouchEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mPlayPauseButton) {
+ if (mIsPlaying) {
+ pause();
+ } else {
+ play();
+ }
+ } else if (view == mBigPlayButton) {
+ if (!mIsPlaying) {
+ mBigPlayButtonLayout.setVisibility(View.GONE);
+ doCountdown();
+ }
+ } else if (view == mExit) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ endIntro();
+ }
+
+ private void endIntro() {
+ mMoviePlaying = false;
+ mIntroControl.setOnClickListener(null);
+ mIntroControl.setVisibility(View.GONE);
+ mIntroVideo.setVisibility(View.GONE);
+ doCountdown();
+ }
+
+ private void processFrame() {
+ long newTime = System.currentTimeMillis();
+ long time = newTime - mLastTime;
+
+ boolean end = false;
+
+ if (time > 60) {
+ Log.e("LONG", "Frame time took too long! Time: " + time + " Last process frame: "
+ + mLastFrameTime + " Count: " + mBackgroundCount + " Level: " + mLevel);
+ }
+
+ // We don't want to jump too far so, if real time is > 60 treat it as 33. On screen will seem to slow
+ // down instaead of "jump"
+ if (time > 60) {
+ time = 33;
+ }
+
+ // Score is based on time + presents. Right now 100 point per second played. No presents yet
+ if (mLevel < 6) {
+ mScore += time;
+ }
+
+ if (mIsTv) {
+ mScoreText.setText(mScoreLabel + ": "
+ + NumberFormat.getNumberInstance().format((mScore / 10)));
+ } else {
+ mScoreText.setText(NumberFormat.getNumberInstance().format((mScore / 10)));
+ }
+
+ float scroll = mElfVelX * time;
+
+ // Do collision detection first...
+ // The elf can't collide if it is within 2 seconds of colliding previously.
+ if (mElfIsHit) {
+ if ((newTime - mElfHitTime) > 2000) {
+ // Move to next state.
+ if (mElfState < 4) {
+ mElfState++;
+ AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket),
+ getString(R.string.analytics_action_rocket_hit), null, mElfState);
+ if (mElfState == 4) {
+ mSoundPool.play(mGameOverSound, 1.0f, 1.0f, 2, 0, 1.0f);
+ // No more control...
+ mControlView.setOnTouchListener(null);
+ mElfAccelY = 0.0f;
+ if (mJetThrustStream != 0) {
+ mSoundPool.stop(mJetThrustStream);
+ }
+ }
+ }
+ updateElf(false);
+ mElfIsHit = false;
+ }
+ } else if (mElfState == 4) {
+ // Don't do any collision detection for parachute elf. Just let him fall...
+ } else {
+ // Find the obstacle(s) we might be colliding with. It can only be one of the first 3 obstacles.
+ for (int i = 0; i < 3; i++) {
+ View view = mObstacleLayout.getChildAt(i);
+ if (view == null) {
+ // No more obstacles...
+ break;
+ }
+
+ int[] tmp = new int[2];
+ view.getLocationOnScreen(tmp);
+
+ // If the start of this view is past the center of the elf, we are done
+ if (tmp[0] > mElfPosX) {
+ break;
+ }
+
+ if (RelativeLayout.class.isInstance(view)) {
+ // this is an obstacle layout.
+ View topView = view.findViewById(R.id.top_view);
+ View bottomView = view.findViewById(R.id.bottom_view);
+ if ((topView != null) && topView.getVisibility() == View.VISIBLE) {
+ topView.getLocationOnScreen(tmp);
+ Rect obsRect = new Rect(tmp[0], tmp[1], tmp[0] + topView.getWidth(),
+ tmp[1] + topView.getHeight());
+ if (obsRect.contains((int) mElfPosX,
+ (int) mElfPosY + mElfBitmap.getHeight() / 2)) {
+ handleCollision();
+ }
+ }
+ if (!mElfIsHit) {
+ if ((bottomView != null) && bottomView.getVisibility() == View.VISIBLE) {
+ bottomView.getLocationOnScreen(tmp);
+ Rect obsRect = new Rect(tmp[0], tmp[1], tmp[0] + bottomView.getWidth(),
+ tmp[1] + bottomView.getHeight());
+ if (obsRect.contains((int) mElfPosX,
+ (int) mElfPosY + mElfBitmap.getHeight() / 2)) {
+ // Special case for the mammoth obstacle...
+ if (bottomView.getTag() != null) {
+ if (((mElfPosX - tmp[0]) / (float) bottomView.getWidth())
+ > 0.25f) {
+ // We are over the mammoth not the spike. lower the top of the rect and test again.
+ obsRect.top = (int) (tmp[1] + (
+ (float) bottomView.getHeight() * 0.18f));
+ if (obsRect.contains((int) mElfPosX,
+ (int) mElfPosY + mElfBitmap.getHeight() / 2)) {
+ handleCollision();
+ }
+ }
+ } else {
+ handleCollision();
+ }
+ }
+ }
+ }
+ } else if (FrameLayout.class.isInstance(view)) {
+ // Present view
+ FrameLayout frame = (FrameLayout) view;
+ if (frame.getChildCount() > 0) {
+ ImageView presentView = (ImageView) frame.getChildAt(0);
+ presentView.getLocationOnScreen(tmp);
+ Rect presentRect = new Rect(tmp[0], tmp[1], tmp[0] + presentView.getWidth(),
+ tmp[1] + presentView.getHeight());
+ mElfLayout.getLocationOnScreen(tmp);
+ Rect elfRect = new Rect(tmp[0], tmp[1], tmp[0] + mElfLayout.getWidth(),
+ tmp[1] + mElfLayout.getHeight());
+ if (elfRect.intersect(presentRect)) {
+ // We got a present!
+ mPresentCount++;
+ if (mPresentCount < 4) {
+ mSoundPool.play(mScoreSmallSound, 1.0f, 1.0f, 2, 0, 1.0f);
+ mScore += 1000; // 100 points. Score is 10x displayed score.
+ mPlus100.setVisibility(View.VISIBLE);
+ if (mElfPosY > (mScreenHeight / 2)) {
+ mPlus100.setY(mElfPosY - (mElfLayout.getHeight() + mPlus100
+ .getHeight()));
+ } else {
+ mPlus100.setY(mElfPosY + mElfLayout.getHeight());
+ }
+ mPlus100.setX(mElfPosX);
+ if (m100Anim.hasStarted()) {
+ m100Anim.reset();
+ }
+ mPlus100.startAnimation(m100Anim);
+ } else {
+ mSoundPool.play(mScoreBigSound, 1.0f, 1.0f, 2, 0, 1.0f);
+ mScore += 5000; // 500 points. Score is 10x displayed score.
+ if (!mRainingPresents) {
+ mPresentCount = 0;
+ }
+ mPlus500.setVisibility(View.VISIBLE);
+ if (mElfPosY > (mScreenHeight / 2)) {
+ mPlus500.setY(mElfPosY - (mElfLayout.getHeight() + mPlus100
+ .getHeight()));
+ } else {
+ mPlus500.setY(mElfPosY + mElfLayout.getHeight());
+ }
+ mPlus500.setX(mElfPosX);
+ if (m500Anim.hasStarted()) {
+ m500Anim.reset();
+ }
+ mPlus500.startAnimation(m500Anim);
+ mPresentBonus = true;
+ }
+ frame.removeView(presentView);
+ } else if (elfRect.left > presentRect.right) {
+ mPresentCount = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if (mForegroundLayout.getChildCount() > 0) {
+ int currentX = mForegroundScroll.getScrollX();
+ View view = mForegroundLayout.getChildAt(0);
+ int newX = currentX + (int) scroll;
+ if (newX > view.getWidth()) {
+ newX -= view.getWidth();
+ mForegroundLayout.removeViewAt(0);
+ }
+ mForegroundScroll.setScrollX(newX);
+ }
+
+ // Scroll obstacle views
+ if (mObstacleLayout.getChildCount() > 0) {
+ int currentX = mObstacleScroll.getScrollX();
+ View view = mObstacleLayout.getChildAt(0);
+ int newX = currentX + (int) scroll;
+ if (newX > view.getWidth()) {
+ newX -= view.getWidth();
+ mObstacleLayout.removeViewAt(0);
+ }
+ mObstacleScroll.setScrollX(newX);
+ }
+
+ // Scroll the background and foreground
+ if (mBackgroundLayout.getChildCount() > 0) {
+ int currentX = mBackgroundScroll.getScrollX();
+ View view = mBackgroundLayout.getChildAt(0);
+ int newX = currentX + (int) scroll;
+ if (newX > view.getWidth()) {
+ newX -= view.getWidth();
+ mBackgroundLayout.removeViewAt(0);
+ if (view.getTag() != null) {
+ Pair pair = (Pair) view.getTag();
+ int type = pair.first;
+ int level = pair.second;
+ if (type == 0) {
+ if (mBackgrounds[level] != null) {
+ mBackgrounds[level].recycle();
+ mBackgrounds[level] = null;
+ } else if (mBackgrounds2[level] != null) {
+ mBackgrounds2[level].recycle();
+ mBackgrounds2[level] = null;
+ }
+ } else if (type == 1) {
+ if (mExitTransitions[level] != null) {
+ mExitTransitions[level].recycle();
+ mExitTransitions[level] = null;
+ }
+ } else if (type == 2) {
+ if (mEntryTransitions[level] != null) {
+ mEntryTransitions[level].recycle();
+ mEntryTransitions[level] = null;
+ }
+ }
+ }
+ if (mBackgroundCount == 5) {
+ if (mLevel < 6) {
+ // Pre-fetch next levels backgrounds
+ // end level uses the index 1 background...
+ int level = (mLevel == 5) ? 1 : (mLevel + 1);
+ BackgroundLoadTask task = new BackgroundLoadTask(getResources(),
+ mLevel + 1,
+ BACKGROUNDS[level],
+ EXIT_TRANSITIONS[mLevel],
+ // Exit transitions are for the current level...
+ ENTRY_TRANSITIONS[level],
+ mScaleX,
+ mScaleY,
+ mBackgrounds,
+ mBackgrounds2,
+ mExitTransitions,
+ mEntryTransitions,
+ mScreenWidth,
+ mScreenHeight);
+ task.execute();
+ addNextImages(mLevel, true);
+ addNextObstacles(mLevel, 2);
+ }
+ // Fetch first set of obstacles if the next level changes from woods to cave or cave to factory
+ if (mLevel == 1) {
+ // Next level will be caves. Get bitmaps for the first 20 obstacles.
+ ObstacleLoadTask task = new ObstacleLoadTask(getResources(),
+ CAVE_OBSTACLES,
+ mCaveObstacles,
+ mCaveObstacleList,
+ 0,
+ 2,
+ mScaleX,
+ mScaleY);
+ task.execute();
+ } else if (mLevel == 3) {
+ // Next level will be factory. Get bitmaps for the first 20 obstacles.
+ ObstacleLoadTask task = new ObstacleLoadTask(getResources(),
+ FACTORY_OBSTACLES,
+ mFactoryObstacles,
+ mFactoryObstacleList,
+ 0,
+ 2,
+ mScaleX,
+ mScaleY);
+ task.execute();
+ }
+ mBackgroundCount++;
+ } else if (mBackgroundCount == 7) {
+ // Add transitions and/or next level
+ if (mLevel < 5) {
+ addNextTransitionImages(mLevel + 1);
+ if (mTransitionImagesCount > 0) {
+ addNextObstacleSpacer(mTransitionImagesCount);
+ }
+ addNextImages(mLevel + 1);
+ // First screen of each new level has no obstacles
+ if ((mLevel % 2) == 1) {
+ addNextObstacleSpacer(1);
+ addNextObstacles(mLevel + 1, 1);
+ } else {
+ addNextObstacles(mLevel + 1, 2);
+ }
+ } else if (mLevel == 5) {
+ addNextTransitionImages(mLevel + 1);
+ if (mTransitionImagesCount > 0) {
+ addNextObstacleSpacer(mTransitionImagesCount);
+ }
+ addFinalImages();
+ }
+ mBackgroundCount++;
+ } else if (mBackgroundCount == 9) {
+ // Either the transition or the next level is showing
+ if (this.mTransitionImagesCount > 0) {
+ mTransitionImagesCount--;
+ } else {
+ if (mLevel == 1) {
+ // Destroy the wood obstacle bitmaps
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mWoodObstacles) {
+ for (Bitmap bmp : mWoodObstacles.values()) {
+ bmp.recycle();
+ }
+ mWoodObstacles.clear();
+ }
+ }
+ });
+ thread.start();
+ } else if (mLevel == 3) {
+ // Destroy the cave obstacle bitmaps
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mCaveObstacles) {
+ for (Bitmap bmp : mCaveObstacles.values()) {
+ bmp.recycle();
+ }
+ mCaveObstacles.clear();
+ }
+ }
+ });
+ thread.start();
+ } else if (mLevel == 5) {
+ // Destroy the factory obstacle bitmaps
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mFactoryObstacles) {
+ for (Bitmap bmp : mFactoryObstacles.values()) {
+ bmp.recycle();
+ }
+ mFactoryObstacles.clear();
+ }
+ }
+ });
+ thread.start();
+ }
+ mLevel++;
+
+ // Add an event for clearing this level - note we don't increment mLevel as
+ // it's 0-based and we're tracking the previous level.
+ AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket),
+ getString(R.string.analytics_action_rocket_level), null, mLevel);
+
+ // Achievements
+ if (!mHitLevel) {
+ mCleanLevel = true;
+ }
+ mHitLevel = false;
+ if (mLevel == 5) {
+ mPlus100.setSelected(true);
+ mPlus500.setSelected(true);
+ } else if (mLevel == 6) {
+ mPlus100.setSelected(false);
+ mPlus500.setSelected(false);
+ }
+ if (mLevel < 6) {
+ mSoundPool.play(mLevelUpSound, 1.0f, 1.0f, 2, 0, 1.0f);
+ addNextImages(mLevel);
+ addNextObstacles(mLevel, 2);
+ }
+ mBackgroundCount = 0;
+ }
+ } else {
+ if ((mBackgroundCount % 2) == 1) {
+ if (mLevel < 6) {
+ addNextImages(mLevel);
+ addNextObstacles(mLevel, 2);
+ }
+ }
+ mBackgroundCount++;
+ }
+ }
+ int current = mBackgroundScroll.getScrollX();
+ mBackgroundScroll.setScrollX(newX);
+ if ((mLevel == 6) && (mBackgroundScroll.getScrollX() == current)) {
+ end = true;
+ }
+ }
+
+ // Check on the elf
+ boolean hitBottom = false;
+ boolean hitTop = false;
+
+ float deltaY = mElfVelY * time;
+ mElfPosY = mElfLayout.getY() + deltaY;
+ if (mElfPosY < 0.0f) {
+ mElfPosY = 0.0f;
+ mElfVelY = 0.0f;
+ hitTop = true;
+ } else if (mElfPosY > (mScreenHeight - mElfLayout.getHeight())) {
+ mElfPosY = mScreenHeight - mElfLayout.getHeight();
+ mElfVelY = 0.0f;
+ hitBottom = true;
+ } else {
+ // Remember -Y is up!
+ mElfVelY += (mGravityAccelY * time - mElfAccelY * time);
+ }
+ mElfLayout.setY(mElfPosY);
+
+ // Rotate the elf to indicate thrust, dive.
+ float rot = (float) (Math.atan(mElfVelY / mElfVelX) * 120.0 / Math.PI);
+ mElfLayout.setRotation(rot);
+
+ mElf.invalidate();
+
+ // Update the time and spawn the next call to processFrame.
+ mLastTime = newTime;
+ mLastFrameTime = System.currentTimeMillis() - newTime;
+ if (!end) {
+ if ((mElfState < 4) || !hitBottom) {
+ if (mLastFrameTime < 16) {
+ mHandler.postDelayed(mGameLoop, 16 - mLastFrameTime);
+ } else {
+ mHandler.post(mGameLoop);
+ }
+ } else {
+ endGame();
+ }
+ } else {
+ // Whatever the final stuff is, do it here.
+ mPlayPauseButton.setEnabled(false);
+ mPlayPauseButton.setVisibility(View.INVISIBLE);
+ endGame();
+ }
+ }
+
+ private void handleCollision() {
+ // Achievements
+ mHit = true;
+ mHitLevel = true;
+
+ // Collision!
+ mElfIsHit = true;
+ mElfHitTime = System.currentTimeMillis();
+ updateElf(true);
+ mVibrator.vibrate(500);
+ if (mElfState == 0) {
+ mSoundPool.play(mCrashSound1, 1.0f, 1.0f, 2, 0, 1.0f);
+ } else if (mElfState == 1) {
+ mSoundPool.play(mCrashSound2, 1.0f, 1.0f, 2, 0, 1.0f);
+ } else if (mElfState == 2) {
+ mSoundPool.play(mCrashSound3, 1.0f, 1.0f, 2, 0, 1.0f);
+ } else if (mElfState == 3) {
+ mSoundPool.play(mCrashSound3, 1.0f, 1.0f, 2, 0, 1.0f);
+ }
+ }
+
+ private void doCountdown() {
+ mCountdownStarted = true;
+ mPlayPauseButton.setEnabled(false);
+ // Start the countdown
+ if (mCountdown == null){
+ mCountdown = (TextView) findViewById(R.id.countdown_text);
+ }
+ mCountdown.setVisibility(View.VISIBLE);
+ mCountdown.setTextColor(Color.WHITE);
+ mCountdown.setText("3");
+ mCountDownTimer = new CountDownTimer(3500, 100) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int time = (int) ((millisUntilFinished + 500) / 1000);
+ if (time == 3) {
+ mCountdown.setText("3");
+ } else if (time == 2) {
+ mCountdown.setText("2");
+ } else if (time == 1) {
+ mCountdown.setText("1");
+ } else if (time == 0) {
+ mCountdown.setText("Go!");
+ }
+ }
+
+ @Override
+ public void onFinish() {
+ mCountdownStarted = false;
+ mPlayPauseButton.setEnabled(true);
+ play();
+ mCountdown.setVisibility(View.GONE);
+ }
+ };
+ mCountDownTimer.start();
+ }
+
+ private int mLastObstacle = 0;
+ // 0 - spacer, 1 - upper obstacle, 2 - lower obstacle. These are flags so top + bottom is 3.
+
+ private void addFirstScreenPresents() {
+ // First 4 slots have no nothing.
+ for (int i = 0; i < Math.min(4, SLOTS_PER_SCREEN); i++) {
+ View view = new View(this);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth, mScreenHeight);
+ mObstacleLayout.addView(view, lp);
+ }
+
+ // Generate a SIN like pattern;
+ float center = (float) ((mScreenHeight - mGiftBoxes[0].getHeight()) / 2);
+ float presentHeight = (float) mGiftBoxes[0].getHeight();
+ float[] heights = new float[]{
+ center,
+ center - presentHeight,
+ center - (1.5f * presentHeight),
+ center - presentHeight,
+ center,
+ center + presentHeight,
+ center + (1.5f * presentHeight),
+ center + presentHeight,
+ center
+ };
+ // Add presents to the end
+ if (SLOTS_PER_SCREEN > 4) {
+ for (int i = 0; i < (SLOTS_PER_SCREEN - 4); i++) {
+ // Which one?
+ Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)];
+ ImageView iv = new ImageView(this);
+ iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ iv.setImageBitmap(bmp);
+
+ // Position the present
+ float left = (mSlotWidth - bmp.getWidth()) / 2;
+ float top = heights[(i % heights.length)];
+
+ FrameLayout frame = new FrameLayout(this);
+ LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ frame.addView(iv, flp);
+ iv.setTranslationX(left);
+ iv.setTranslationY(top);
+
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ mObstacleLayout.addView(frame, lp);
+ }
+ }
+
+ // Account for rounding errors in mSlotWidth
+ int extra = (mScreenWidth - (SLOTS_PER_SCREEN * mSlotWidth));
+ if (extra > 0) {
+ // Add filler to ensure sync with background/foreground scrolls!
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ View view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ }
+
+ mLastObstacle = 0;
+ }
+
+ private void addFinalPresentRun() {
+ // Two spacers at the begining.
+ View view = new View(this);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mSlotWidth, mScreenHeight);
+ mObstacleLayout.addView(view, lp);
+ view = new View(this);
+ mObstacleLayout.addView(view, lp);
+
+ // All of these presents are 500 points (but only if you're awesome)
+ if (mElfState == 0) {
+ mRainingPresents = true;
+ }
+
+ // SIN wave of presents in the middle
+ float center = (float) (mScreenHeight / 2);
+ float amplitude = (float) (mScreenHeight / 4);
+
+ int count = (3 * SLOTS_PER_SCREEN) - 4;
+
+ for (int i = 0; i < count; i++) {
+ float x = (float) ((mSlotWidth - mGiftBoxes[0].getWidth()) / 2);
+ float y = center + (amplitude * (float) Math
+ .sin(2.0 * Math.PI * (double) i / (double) count));
+ Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)];
+ ImageView iv = new ImageView(this);
+ iv.setImageBitmap(bmp);
+ iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ FrameLayout frame = new FrameLayout(this);
+ LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ frame.addView(iv, flp);
+ iv.setTranslationX(x);
+ iv.setTranslationY(y);
+ mObstacleLayout.addView(frame, lp);
+ }
+
+ // Two spacers at the end.
+ view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ view = new View(this);
+ mObstacleLayout.addView(view, lp);
+
+ // Account for rounding errors in mSlotWidth
+ int extra = ((3 * mScreenWidth) - (3 * SLOTS_PER_SCREEN * mSlotWidth));
+ if (extra > 0) {
+ // Add filler to ensure sync with background/foreground scrolls!
+ lp = new LinearLayout.LayoutParams(extra, LinearLayout.LayoutParams.MATCH_PARENT);
+ view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ }
+ }
+
+ // Pre-populate the random list of obstacles so we can background fetch them.
+ private void initObstacles(int[] resources, int stride, ArrayList list) {
+ // Select 200 obstacles randomly. This should be enough for each type of obstacles.
+ // We will wrap if we need more.
+ for (int i = 0; i < 200; i++) {
+ list.add(mRandom.nextInt(resources.length / stride));
+ }
+ }
+
+ // Pre-populate the random list of obstacles and pre-load some of the bitmaps.
+ private void initObstaclesAndPreLoad(int[] resources, int stride, TreeMap map,
+ ArrayList list) {
+ initObstacles(resources, stride, list);
+ // Load the bitmaps for the first 20 obstacles.
+ for (int i = 0; i < 20; i++) {
+ int obstacle = list.get(i);
+ for (int j = (obstacle * stride); j < ((obstacle + 1) * stride); j++) {
+ // Check just in case something is wonky
+ if (j < resources.length) {
+ int id = resources[j];
+ if (id != -1) {
+ // Only need to load it once...
+ if (!map.containsKey(id)) {
+ Bitmap bmp = BitmapFactory.decodeResource(getResources(), id);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp,
+ (int) ((float) bmp.getWidth() * mScaleX),
+ (int) ((float) bmp.getHeight() * mScaleY), false);
+ if (tmp != bmp) {
+ bmp.recycle();
+ }
+ map.put(id, tmp);
+ } else {
+ map.put(id, bmp);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void addNextObstacleSpacer(int screens) {
+ if (screens > 0) {
+ View view = new View(this);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mScreenWidth * screens,
+ mScreenHeight);
+ mObstacleLayout.addView(view, lp);
+ mLastObstacle = 0;
+ }
+ }
+
+ private void addNextObstacles(int level, int screens) {
+ if (level < 2) {
+ addWoodObstacles(screens);
+ } else if (level < 4) {
+ addCaveObstacles(screens);
+ } else {
+ addFactoryObstacles(screens);
+ }
+ }
+
+ private void addWoodObstacles(int screens) {
+ int totalSlots = screens * SLOTS_PER_SCREEN;
+ for (int i = 0; i < totalSlots; ) {
+ // Any given "slot" has a 1 in 3 chance of having an obstacle
+ if (mRandom.nextInt(3) == 0) {
+ View view = mInflater.inflate(R.layout.obstacle_layout, null);
+ ImageView top = (ImageView) view.findViewById(R.id.top_view);
+ ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view);
+ ImageView back = (ImageView) view.findViewById(R.id.back_view);
+
+ // Which obstacle?
+ int width = 0;
+// int obstacle = mRandom.nextInt((WOOD_OBSTACLES.length/3));
+ if ((mWoodObstacleIndex % 20) == 0) {
+ ObstacleLoadTask task = new ObstacleLoadTask(getResources(),
+ WOOD_OBSTACLES,
+ mWoodObstacles,
+ mWoodObstacleList,
+ mWoodObstacleIndex + 20,
+ 3,
+ mScaleX,
+ mScaleY);
+ task.execute();
+ }
+ int obstacle = mWoodObstacleList.get(mWoodObstacleIndex++);
+ if (mWoodObstacleIndex >= mWoodObstacleList.size()) {
+ mWoodObstacleIndex = 0;
+ }
+ int topIndex = obstacle * 3;
+ int bottomIndex = topIndex + 1;
+ int backIndex = topIndex + 2;
+ if (WOOD_OBSTACLES[backIndex] != -1) {
+ Bitmap bmp = null;
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[backIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[backIndex]);
+ if (bmp == null) {
+ try {
+ mWoodObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ width = bmp.getWidth();
+ back.setImageBitmap(bmp);
+ } else {
+ back.setVisibility(View.GONE);
+ }
+
+ int currentObstacle = 0; // Same values as mLastObstacle
+ if (WOOD_OBSTACLES[topIndex] != -1) {
+ currentObstacle |= 1;
+ Bitmap bmp = null;
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[topIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[topIndex]);
+ if (bmp == null) {
+ try {
+ mWoodObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ width = bmp.getWidth();
+ top.setImageBitmap(bmp);
+ } else {
+ top.setVisibility(View.GONE);
+ }
+
+ if (WOOD_OBSTACLES[bottomIndex] != -1) {
+ currentObstacle |= 2;
+ Bitmap bmp = null;
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[bottomIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mWoodObstacles) {
+ bmp = mWoodObstacles.get(WOOD_OBSTACLES[bottomIndex]);
+ if (bmp == null) {
+ try {
+ mWoodObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ if (bmp.getWidth() > width) {
+ width = bmp.getWidth();
+ }
+ bottom.setImageBitmap(bmp);
+ } else {
+ bottom.setVisibility(View.GONE);
+ }
+ int slots = (width / mSlotWidth) + 2;
+
+ // If last obstacle had a top and this is a bottom or vice versa, insert a space
+ if ((mLastObstacle & 0x1) > 0) {
+ if ((currentObstacle & 0x2) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ } else if ((mLastObstacle & 0x2) > 0) {
+ if ((currentObstacle & 0x1) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead
+ if ((i + slots) > totalSlots) {
+ addSpaceOrPresent(mSlotWidth * (totalSlots - i));
+ i = totalSlots;
+ } else {
+ mLastObstacle = currentObstacle;
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ view.setLayoutParams(lp);
+ mObstacleLayout.addView(view);
+ i += slots;
+ }
+ } else {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // Account for rounding errors in mSlotWidth
+ int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth));
+ if (extra > 0) {
+ // Add filler to ensure sync with background/foreground scrolls!
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ View view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ }
+ }
+
+ private void addCaveObstacles(int screens) {
+ int totalSlots = screens * SLOTS_PER_SCREEN;
+ for (int i = 0; i < totalSlots; ) {
+ // Any given "slot" has a 1 in 3 chance of having an obstacle
+ if (mRandom.nextInt(2) == 0) {
+ View view = mInflater.inflate(R.layout.obstacle_layout, null);
+ ImageView top = (ImageView) view.findViewById(R.id.top_view);
+ ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view);
+ ImageView back = (ImageView) view.findViewById(R.id.back_view);
+
+ // Which obstacle?
+ int width = 0;
+ if ((mCaveObstacleIndex % 20) == 0) {
+ ObstacleLoadTask task = new ObstacleLoadTask(getResources(),
+ CAVE_OBSTACLES,
+ mCaveObstacles,
+ mCaveObstacleList,
+ mCaveObstacleIndex + 20,
+ 2,
+ mScaleX,
+ mScaleY);
+ task.execute();
+ }
+ int obstacle = mCaveObstacleList.get(mCaveObstacleIndex++);
+ if (mCaveObstacleIndex >= mCaveObstacleList.size()) {
+ mCaveObstacleIndex = 0;
+ }
+// int obstacle = mRandom.nextInt((CAVE_OBSTACLES.length/2));
+ int topIndex = obstacle * 2;
+ int bottomIndex = topIndex + 1;
+ back.setVisibility(View.GONE);
+
+ int currentObstacle = 0; // Same values as mLastObstacle
+ if (CAVE_OBSTACLES[topIndex] != -1) {
+ currentObstacle |= 1;
+ Bitmap bmp = null;
+ synchronized (mCaveObstacles) {
+ bmp = mCaveObstacles.get(CAVE_OBSTACLES[topIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mCaveObstacles) {
+ bmp = mCaveObstacles.get(CAVE_OBSTACLES[topIndex]);
+ if (bmp == null) {
+ try {
+ mCaveObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ width = bmp.getWidth();
+ top.setImageBitmap(bmp);
+ } else {
+ top.setVisibility(View.GONE);
+ }
+
+ if (CAVE_OBSTACLES[bottomIndex] != -1) {
+ currentObstacle |= 2;
+ Bitmap bmp = null;
+ synchronized (mCaveObstacles) {
+ bmp = mCaveObstacles.get(CAVE_OBSTACLES[bottomIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mCaveObstacles) {
+ bmp = mCaveObstacles.get(CAVE_OBSTACLES[bottomIndex]);
+ if (bmp == null) {
+ try {
+ mCaveObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ if (bmp.getWidth() > width) {
+ width = bmp.getWidth();
+ }
+ bottom.setImageBitmap(bmp);
+ if (CAVE_OBSTACLES[bottomIndex] == R.drawable.img_mammoth) {
+ // Special case...
+ bottom.setTag(true);
+ }
+ } else {
+ bottom.setVisibility(View.GONE);
+ }
+ int slots = (width / mSlotWidth);
+ slots += 2;
+
+ // If last obstacle had a top and this is a bottom or vice versa, insert a space
+ if ((mLastObstacle & 0x1) > 0) {
+ if ((currentObstacle & 0x2) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ } else if ((mLastObstacle & 0x2) > 0) {
+ if ((currentObstacle & 0x1) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead
+ if ((i + slots) > totalSlots) {
+ addSpaceOrPresent(mSlotWidth * (totalSlots - i));
+ i = totalSlots;
+ } else {
+ mLastObstacle = currentObstacle;
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ view.setLayoutParams(lp);
+ mObstacleLayout.addView(view);
+ i += slots;
+ }
+ } else {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // Account for rounding errors in mSlotWidth
+ int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth));
+ if (extra > 0) {
+ // Add filler to ensure sync with background/foreground scrolls!
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ View view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ }
+ }
+
+ private void addFactoryObstacles(int screens) {
+ int totalSlots = screens * SLOTS_PER_SCREEN;
+ for (int i = 0; i < totalSlots; ) {
+ // Any given "slot" has a 1 in 3 chance of having an obstacle
+ if (mRandom.nextInt(2) == 0) {
+ View view = mInflater.inflate(R.layout.obstacle_layout, null);
+ ImageView top = (ImageView) view.findViewById(R.id.top_view);
+ ImageView bottom = (ImageView) view.findViewById(R.id.bottom_view);
+ ImageView back = (ImageView) view.findViewById(R.id.back_view);
+
+ // Which obstacle?
+ int width = 0;
+// int obstacle = mRandom.nextInt((FACTORY_OBSTACLES.length/2));
+ if ((mFactoryObstacleIndex % 20) == 0) {
+ ObstacleLoadTask task = new ObstacleLoadTask(getResources(),
+ FACTORY_OBSTACLES,
+ mFactoryObstacles,
+ mFactoryObstacleList,
+ mFactoryObstacleIndex + 20,
+ 2,
+ mScaleX,
+ mScaleY);
+ task.execute();
+ }
+ int obstacle = mFactoryObstacleList.get(mFactoryObstacleIndex++);
+ if (mFactoryObstacleIndex >= mFactoryObstacleList.size()) {
+ mFactoryObstacleIndex = 0;
+ }
+ int topIndex = obstacle * 2;
+ int bottomIndex = topIndex + 1;
+ back.setVisibility(View.GONE);
+
+ int currentObstacle = 0; // Same values as mLastObstacle
+ if (FACTORY_OBSTACLES[topIndex] != -1) {
+ currentObstacle |= 1;
+ Bitmap bmp = null;
+ synchronized (mFactoryObstacles) {
+ bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[topIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mFactoryObstacles) {
+ bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[topIndex]);
+ if (bmp == null) {
+ try {
+ mFactoryObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ width = bmp.getWidth();
+ top.setImageBitmap(bmp);
+ } else {
+ top.setVisibility(View.GONE);
+ }
+
+ if (FACTORY_OBSTACLES[bottomIndex] != -1) {
+ currentObstacle |= 2;
+ Bitmap bmp = null;
+ synchronized (mFactoryObstacles) {
+ bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[bottomIndex]);
+ }
+ while (bmp == null) {
+ synchronized (mFactoryObstacles) {
+ bmp = mFactoryObstacles.get(FACTORY_OBSTACLES[bottomIndex]);
+ if (bmp == null) {
+ try {
+ mFactoryObstacles.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ if (bmp.getWidth() > width) {
+ width = bmp.getWidth();
+ }
+ bottom.setImageBitmap(bmp);
+ } else {
+ bottom.setVisibility(View.GONE);
+ }
+ int slots = (width / mSlotWidth);
+ if ((width % mSlotWidth) != 0) {
+ slots++;
+ }
+
+ // If last obstacle had a top and this is a bottom or vice versa, insert a space
+ if ((mLastObstacle & 0x1) > 0) {
+ if ((currentObstacle & 0x2) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ } else if ((mLastObstacle & 0x2) > 0) {
+ if ((currentObstacle & 0x1) > 0) {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // If the new obstacle is too wide for the remaining space, skip it and fill spacer instead
+ if ((i + slots) > totalSlots) {
+ addSpaceOrPresent(mSlotWidth * (totalSlots - i));
+ i = totalSlots;
+ } else {
+ mLastObstacle = currentObstacle;
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(slots * mSlotWidth,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ view.setLayoutParams(lp);
+ mObstacleLayout.addView(view);
+ i += slots;
+ }
+ } else {
+ addSpaceOrPresent(mSlotWidth);
+ i++;
+ }
+ }
+
+ // Account for rounding errors in mSlotWidth
+ int extra = ((screens * mScreenWidth) - (totalSlots * mSlotWidth));
+ if (extra > 0) {
+ // Add filler to ensure sync with background/foreground scrolls!
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(extra,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ View view = new View(this);
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mObstacleLayout.addView(view, lp);
+ }
+ }
+
+ private void addSpaceOrPresent(int width) {
+ if (width > 0) {
+ mLastObstacle = 0;
+ // 1/3 chance of a present.
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(width,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ if (mRandom.nextInt(3) == 0) {
+ // Present!
+
+ // Which one?
+ Bitmap bmp = mGiftBoxes[mRandom.nextInt(mGiftBoxes.length)];
+ ImageView iv = new ImageView(this);
+ iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ iv.setImageBitmap(bmp);
+
+ // Position the present
+ int left = mRandom.nextInt(width / 2) + (width / 4) - (
+ (int) ((float) bmp.getWidth() * mScaleX) / 2);
+ int top = mRandom.nextInt(mScreenHeight / 2) + (mScreenHeight / 4) - (
+ (int) ((float) bmp.getHeight() * mScaleY) / 2);
+
+ FrameLayout frame = new FrameLayout(this);
+ LayoutParams flp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ frame.addView(iv, flp);
+ iv.setTranslationX(left);
+ iv.setTranslationY(top);
+
+ mObstacleLayout.addView(frame, lp);
+ } else {
+ // Space
+ View view = new View(this);
+ mObstacleLayout.addView(view, lp);
+ }
+ }
+ }
+
+ private void addNextImages(int level) {
+ addNextImages(level, false);
+ }
+
+ private void addNextImages(int level, boolean recycle) {
+ if (level < BACKGROUNDS.length) {
+ // Add the background image
+ ImageView iv = new ImageView(this);
+ iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ // This is being background loaded. Should already be loaded, but if not, wait.
+ while (mBackgrounds[level] == null) {
+ synchronized (mBackgrounds) {
+ if (mBackgrounds[level] == null) {
+ try {
+ mBackgrounds.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ iv.setImageBitmap(mBackgrounds[level]);
+ if (recycle) {
+ iv.setTag(new Pair(0, level));
+ }
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ mBackgroundLayout.addView(iv, lp);
+ iv = new ImageView(this);
+ iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ if (recycle) {
+ iv.setTag(new Pair(0, level));
+ }
+ iv.setImageBitmap(mBackgrounds2[level]);
+ mBackgroundLayout.addView(iv, lp);
+
+ // Add the foreground image
+ if (FOREGROUNDS[level] == -1) {
+ View view = new View(this);
+ lp = new LinearLayout.LayoutParams(mScreenWidth * 2, 10);
+ mForegroundLayout.addView(view, lp);
+ } else {
+ iv = new ImageView(this);
+ iv.setBackgroundResource(R.drawable.img_snow_ground_tiles);
+ if (recycle) {
+ iv.setTag(level);
+ }
+ lp = new LinearLayout.LayoutParams(mScreenWidth * 2,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ mForegroundLayout.addView(iv, lp);
+ iv = new ImageView(this);
+ if (recycle) {
+ iv.setTag(level);
+ }
+ iv.setBackgroundResource(R.drawable.img_snow_ground_tiles);
+ mForegroundLayout.addView(iv, lp);
+ }
+ }
+ }
+
+ // This is the level we are moving TO.
+ private void addNextTransitionImages(int level) {
+ mTransitionImagesCount = 0;
+ if ((level > 0) && ((level - 1) < EXIT_TRANSITIONS.length)) {
+ if (EXIT_TRANSITIONS[level - 1] != -1) {
+ // Add the exit transition image
+ ImageView iv = new ImageView(this);
+ iv.setTag(new Pair(1, (level - 1)));
+ // This is being background loaded. Should already be loaded, but if not, wait.
+ while (mExitTransitions[level - 1] == null) {
+ synchronized (mExitTransitions) {
+ if (mExitTransitions[level - 1] == null) {
+ try {
+ mExitTransitions.wait();
+ } catch (InterruptedException e) {
+ continue;
+ }
+ }
+ }
+ }
+ iv.setImageBitmap(mExitTransitions[level - 1]);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ mBackgroundLayout.addView(iv, lp);
+
+ // No foreground on transistions. Transition images are a single screen long
+ View view = new View(this);
+ lp = new LinearLayout.LayoutParams(mScreenWidth, 10);
+ mForegroundLayout.addView(view, lp);
+ mTransitionImagesCount++;
+ }
+ }
+ if ((level > 0) && (level < ENTRY_TRANSITIONS.length)) {
+ if (ENTRY_TRANSITIONS[level] != -1) {
+ // Add the exit transition image
+ ImageView iv = new ImageView(this);
+ iv.setTag(new Pair(2, level));
+ // This is being background loaded. Should already be loaded, but if not, wait.
+ while (mEntryTransitions[level] == null) {
+ synchronized (mEntryTransitions) {
+ if (mEntryTransitions[level] == null) {
+ try {
+ mEntryTransitions.wait();
+ } catch (InterruptedException e) {
+ continue;
+ }
+ }
+ }
+ }
+ iv.setImageBitmap(mEntryTransitions[level]);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ mBackgroundLayout.addView(iv, lp);
+ // No foreground on transistions. Transition images are a single screen long
+ View view = new View(this);
+ lp = new LinearLayout.LayoutParams(mScreenWidth, 10);
+ mForegroundLayout.addView(view, lp);
+ mTransitionImagesCount++;
+ }
+ }
+ }
+
+ private void addFinalImages() {
+ addNextImages(1);
+ addNextImages(1);
+
+ // Add presents
+ addFinalPresentRun();
+
+ // Add final screen. This is a two screen background.
+ ImageView iv = new ImageView(this);
+ iv.setTag(true);
+ Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.bg_finish);
+ if ((mScaleX != 1.0f) || (mScaleY != 1.0f)) {
+ Bitmap tmp = Bitmap.createScaledBitmap(bmp, mScreenWidth * 2, mScreenHeight, false);
+ if (bmp != tmp) {
+ bmp.recycle();
+ }
+ bmp = tmp;
+ }
+ iv.setImageBitmap(bmp);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ mBackgroundLayout.addView(iv, lp);
+ View view = new View(this);
+ lp = new LinearLayout.LayoutParams(mScreenWidth * 2, 10);
+ mForegroundLayout.addView(view, lp);
+ addNextObstacleSpacer(2);
+ }
+
+ // Load the level 1 images right now since we need to display them.
+ // Load the rest on a thread.
+ // We preload all of these because the transitions can be quick.
+ // They are not very big, relatively speaking.
+ private void loadElfImages() {
+ mElfImages = new Bitmap[ELF_IMAGES.length];
+ mElfHitImages = new Bitmap[ELF_HIT_IMAGES.length];
+ mElfBurnImages = new Bitmap[ELF_BURN_IMAGES.length];
+ mElfThrustImages = new Bitmap[ELF_THRUST_IMAGES.length];
+ mElfSmokeImages = new Bitmap[ELF_SMOKE_IMAGES.length];
+ mElfImages[0] = BitmapFactory.decodeResource(getResources(), ELF_IMAGES[0]);
+ mElfHitImages[0] = BitmapFactory.decodeResource(getResources(), ELF_HIT_IMAGES[0]);
+ mElfBurnImages[0] = BitmapFactory.decodeResource(getResources(), ELF_BURN_IMAGES[0]);
+ mElfThrustImages[0] = BitmapFactory.decodeResource(getResources(), ELF_THRUST_IMAGES[0]);
+ mElfSmokeImages[0] = BitmapFactory.decodeResource(getResources(), ELF_SMOKE_IMAGES[0]);
+
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ for (int i = 1; i < ELF_IMAGES.length; i++) {
+ mElfImages[i] = BitmapFactory.decodeResource(getResources(), ELF_IMAGES[i]);
+ }
+ for (int i = 1; i < ELF_HIT_IMAGES.length; i++) {
+ mElfHitImages[i] = BitmapFactory
+ .decodeResource(getResources(), ELF_HIT_IMAGES[i]);
+ }
+ for (int i = 1; i < ELF_BURN_IMAGES.length; i++) {
+ mElfBurnImages[i] = BitmapFactory
+ .decodeResource(getResources(), ELF_BURN_IMAGES[i]);
+ }
+ for (int i = 1; i < ELF_THRUST_IMAGES.length; i++) {
+ mElfThrustImages[i] = BitmapFactory
+ .decodeResource(getResources(), ELF_THRUST_IMAGES[i]);
+ }
+ for (int i = 1; i < ELF_SMOKE_IMAGES.length; i++) {
+ mElfSmokeImages[i] = BitmapFactory
+ .decodeResource(getResources(), ELF_SMOKE_IMAGES[i]);
+ }
+ }
+ });
+ thread.start();
+ }
+
+ private void updateElf(boolean hit) {
+ float thrustWidth = 0.0f;
+ if (hit) {
+ // Just update the elf drawable
+ mElf.setImageDrawable(null);
+ mElfBitmap.recycle();
+ mElfBitmap = mElfHitImages[mElfState];
+ mElf.setImageBitmap(mElfBitmap);
+ updateElfThrust(2);
+ thrustWidth = (float) mCurrentTrailBitmap.getWidth() * mElfScale;
+ } else {
+ // New state for elf recycle, reload and reset.
+ mElf.setImageDrawable(null);
+ if (mElfBitmap != null) {
+ mElfBitmap.recycle();
+ }
+ mThrust.setImageDrawable(null);
+ if (mBurnBitmap != null) {
+ mBurnBitmap.recycle();
+ }
+ if (mThrustBitmap != null) {
+ mThrustBitmap.recycle();
+ }
+ if (mSmokeBitmpap != null) {
+ mSmokeBitmpap.recycle();
+ }
+ if (mElfState < 4) {
+ mBurnBitmap = mElfBurnImages[mElfState];
+ mThrustBitmap = mElfThrustImages[mElfState];
+ mSmokeBitmpap = mElfSmokeImages[mElfState];
+ mElfBitmap = mElfImages[mElfState];
+ if (mElfAccelY > 0.0f) {
+ updateElfThrust(1);
+ } else {
+ updateElfThrust(0);
+ }
+ thrustWidth = (float) mCurrentTrailBitmap.getWidth() * mElfScale;
+ } else {
+ mElfBitmap = mElfImages[4];
+ mThrust.setVisibility(View.GONE);
+ }
+ mElf.setImageBitmap(mElfBitmap);
+ }
+ float offset = thrustWidth + ((float) mElfBitmap.getWidth() / 2.0f);
+ mElfLayout.setX(mElfPosX - offset);
+ mElfLayout.setY(mElfPosY);
+ mElfLayout.setPivotX(offset);
+ mElfLayout.setPivotY((float) mElfBitmap.getHeight() * 3.0f / 4.0f);
+ float rot = (float) (Math.atan(mElfVelY / mElfVelX) * 120.0 / Math.PI);
+ mElfLayout.setRotation(rot);
+ mElfLayout.invalidate();
+ }
+
+ // 0 - burn, 1 - thrust, 2 - smoke, 3 - gone
+ private void updateElfThrust(int type) {
+ switch (type) {
+ case 0:
+ mCurrentTrailBitmap = mBurnBitmap;
+ break;
+ case 1:
+ mCurrentTrailBitmap = mThrustBitmap;
+ break;
+ case 2:
+ mCurrentTrailBitmap = mSmokeBitmpap;
+ break;
+ case 3:
+ default:
+ mCurrentTrailBitmap = null;
+ break;
+ }
+ if (mCurrentTrailBitmap != null) {
+ mThrust.setImageBitmap(mCurrentTrailBitmap);
+ }
+ }
+
+ private void pause() {
+
+ if (mIsPlaying) {
+ mBigPlayButtonLayout.setVisibility(View.VISIBLE);
+ if (!mIsTv) {
+ mExit.setVisibility(View.VISIBLE);
+ }
+ }
+
+ mIsPlaying = false;
+ mHandler.removeCallbacks(mGameLoop);
+ mControlView.setOnTouchListener(null);
+ mPlayPauseButton.setImageResource(R.drawable.play_button_jp);
+
+ if (mJetThrustStream > 0) {
+ mSoundPool.stop(mJetThrustStream);
+ mJetThrustStream = 0;
+ }
+ if (mBackgroundPlayer != null) {
+ mBackgroundPlayer.pause();
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(getWindow());
+ }
+ }
+
+ private void play() {
+ mIsPlaying = true;
+ mBigPlayButtonLayout.setVisibility(View.GONE);
+ mLastTime = System.currentTimeMillis();
+ mControlView.setOnTouchListener(this);
+ mHandler.post(mGameLoop);
+ mPlayPauseButton.setImageResource(R.drawable.pause_button_jp);
+ mExit.setVisibility(View.GONE);
+ if (mBackgroundPlayer != null) {
+ mBackgroundPlayer.start();
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ ImmersiveModeHelper.setImmersiveSticky(getWindow());
+ }
+ }
+
+ private void endGame() {
+ mIsPlaying = false;
+ Intent intent = new Intent(this.getApplicationContext(), EndGameActivity.class);
+ intent.putExtra("score", (long) (mScore / 10));
+
+ AnalyticsManager.sendEvent(getString(R.string.analytics_screen_rocket),
+ getString(R.string.analytics_action_rocket_final_score), null, mScore / 10);
+
+ if (mCleanLevel) {
+ intent.putExtra(getString(R.string.achievement_safe_tapper), true);
+ }
+ if (!mHit) {
+ intent.putExtra(getString(R.string.achievement_untouchable), true);
+ }
+ if (mPresentBonus) {
+ intent.putExtra(getString(R.string.achievement_hidden_presents), true);
+ }
+ if ((mScore / 10) > 10000) {
+ intent.putExtra(getString(R.string.achievement_rocket_junior_score_10000), true);
+ }
+ if ((mScore / 10) > 30000) {
+ intent.putExtra(getString(R.string.achievement_rocket_intermediate_score_30000), true);
+ }
+ if ((mScore / 10) > 50000) {
+ intent.putExtra(getString(R.string.achievement_rocket_pro_score_50000), true);
+ }
+ startActivity(intent);
+ overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
+ finish();
+ }
+
+ private void releaseIntegerBitmapMap(Map map) {
+
+ Iterator> it = map.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = it.next();
+ Bitmap bitmap = entry.getValue();
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+ }
+ }
+
+ private void releaseBitmapArray(Bitmap[] bitmapArray) {
+ if (bitmapArray != null) {
+ for (Bitmap bitmap : bitmapArray) {
+ if (bitmap != null && !bitmap.isRecycled()) {
+ bitmap.recycle();
+ }
+ }
+ }
+ }
+}
diff --git a/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/TvUtil.java b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/TvUtil.java
new file mode 100644
index 000000000..309c2cbec
--- /dev/null
+++ b/rocketsleigh/src/main/java/com/google/android/apps/santatracker/rocketsleigh/TvUtil.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.rocketsleigh;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+
+/**
+ * Handy utility class for supporting TV.
+ */
+public class TvUtil {
+
+ // Check whether this app is running on TV or not.
+ public static boolean isTv(Context context) {
+
+ final UiModeManager
+ manager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
+
+ return manager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
+ }
+}
diff --git a/rocketsleigh/src/main/res/anim/fade_in.xml b/rocketsleigh/src/main/res/anim/fade_in.xml
new file mode 100644
index 000000000..ac05ce28a
--- /dev/null
+++ b/rocketsleigh/src/main/res/anim/fade_in.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/rocketsleigh/src/main/res/anim/fade_out.xml b/rocketsleigh/src/main/res/anim/fade_out.xml
new file mode 100644
index 000000000..9e2d22a13
--- /dev/null
+++ b/rocketsleigh/src/main/res/anim/fade_out.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_finalscore.png
new file mode 100644
index 000000000..a7426f66a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_finish.png
new file mode 100644
index 000000000..1863b0d1b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..441e373b3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..49c87c1ee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..8db5e2bd3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..42b41900d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..8723dcbf1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..e328d0d86
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_1.png
new file mode 100644
index 000000000..6d34b904b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_2.png
new file mode 100644
index 000000000..823c96c12
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_3.png
new file mode 100644
index 000000000..bdc82cd59
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_4.png
new file mode 100644
index 000000000..05298d3b5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-hdpi/btn_pause.png
new file mode 100644
index 000000000..85bda07a2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-hdpi/btn_pause_p.png
new file mode 100644
index 000000000..3ca883c30
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-hdpi/btn_play.png
new file mode 100644
index 000000000..abc92099e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-hdpi/btn_play_p.png
new file mode 100644
index 000000000..4f8ae29eb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay.png
new file mode 100644
index 000000000..9861254bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..ab480ddc5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar.png
new file mode 100644
index 000000000..bcc5f51a7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..64ab3b2cf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/ic_launcher.png b/rocketsleigh/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..63fddf6e5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-hdpi/icn_achievements.png
new file mode 100644
index 000000000..2bb4d5869
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-hdpi/icn_leaderboards.png
new file mode 100644
index 000000000..3f8285700
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-hdpi/icn_play_again.png
new file mode 100644
index 000000000..dd505a437
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-hdpi/icn_sign_in.png
new file mode 100644
index 000000000..f03b48f74
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_100.png b/rocketsleigh/src/main/res/drawable-hdpi/img_100.png
new file mode 100644
index 000000000..f18beaa21
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_100_purp.png
new file mode 100644
index 000000000..c14907b1b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-hdpi/img_2_bats.png
new file mode 100644
index 000000000..c4342ab3d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-hdpi/img_3_bats.png
new file mode 100644
index 000000000..812060e42
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-hdpi/img_4_bats.png
new file mode 100644
index 000000000..ed99a56f6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_500.png b/rocketsleigh/src/main/res/drawable-hdpi/img_500.png
new file mode 100644
index 000000000..f0144461b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_500_purp.png
new file mode 100644
index 000000000..e33a8eadb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-hdpi/img_5_bats.png
new file mode 100644
index 000000000..8a6325cd0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-hdpi/img_bear_big.png
new file mode 100644
index 000000000..96be63002
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-hdpi/img_bear_little.png
new file mode 100644
index 000000000..bee35368a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_0.png
new file mode 100644
index 000000000..5b1fd6bf9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..dfcf5aaab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_top.png
new file mode 100644
index 000000000..1c088b05a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..5a7338e35
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_top.png
new file mode 100644
index 000000000..096c46ac5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..47b02d469
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_top.png
new file mode 100644
index 000000000..1c7640c29
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..061c69fd5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_top.png
new file mode 100644
index 000000000..7bf91ed43
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_bare.png
new file mode 100644
index 000000000..a049169a9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_buttons.png
new file mode 100644
index 000000000..cfb600937
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..dbed190f9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..0c38d50d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..9eeb9291f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..7c4b28c22
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_corn.png
new file mode 100644
index 000000000..96468e40f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-hdpi/img_choco_fountn.png
new file mode 100644
index 000000000..750a75652
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..3456bae29
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..9b3fdd01a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..35fdfea51
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..b56af68f2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..2b4f84fa5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..cb13f39c7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..8d28e339b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..1e2526e7a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-hdpi/img_ice_swan.png
new file mode 100644
index 000000000..3c3c3d948
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_0.png
new file mode 100644
index 000000000..678d0d6a3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_1.png
new file mode 100644
index 000000000..c88a3defc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_2.png
new file mode 100644
index 000000000..c32da0359
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_3.png
new file mode 100644
index 000000000..9febbf2d5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_drop.png
new file mode 100644
index 000000000..80a4c7aa7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..ec6ebdc9c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..ae9307159
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..2d8399ae2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..0c61bef5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..f047eff4a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..314ce196b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..828b48df2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..8d69dac5f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..a7d5137a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..dd4cbb3a3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..aa645d0b6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..82b519bba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..637fc7387
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..aa645d0b6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..e26311155
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..3a8c343e6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..39e86a37a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..e26311155
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust.png
new file mode 100644
index 000000000..2286f4796
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..050e235e0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..8164aee3f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..fdfa15a6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..050e235e0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_0.png
new file mode 100644
index 000000000..2bf6b895e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100.png
new file mode 100644
index 000000000..1b11a4d3e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..80d639414
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25.png
new file mode 100644
index 000000000..477b0a800
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..1cc13152f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50.png
new file mode 100644
index 000000000..e27ef57c5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..482113675
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75.png
new file mode 100644
index 000000000..480adad22
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..757d7cf54
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-hdpi/img_log_elf.png
new file mode 100644
index 000000000..01e2e6ee8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-hdpi/img_lollipops.png
new file mode 100644
index 000000000..15bf55c71
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mammoth.png
new file mode 100644
index 000000000..c1829efac
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..59d0aa94c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..e4762dbc6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_gondola.png
new file mode 100644
index 000000000..fdc1eb1d5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..e1399ad55
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..552b65b05
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-hdpi/img_owl.png
new file mode 100644
index 000000000..3652602c9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_0.png
new file mode 100644
index 000000000..de6682b20
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..e3b85a12b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_top.png
new file mode 100644
index 000000000..ad24894da
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..c873a5a81
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_top.png
new file mode 100644
index 000000000..6502ed802
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..ae8824248
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_top.png
new file mode 100644
index 000000000..73ff543be
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..3a7862918
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_top.png
new file mode 100644
index 000000000..ac2c5fffa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_bare.png
new file mode 100644
index 000000000..a58d20300
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_snow_ground_a_tile.png b/rocketsleigh/src/main/res/drawable-hdpi/img_snow_ground_a_tile.png
new file mode 100644
index 000000000..804c8cfce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_snow_ground_a_tile.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-hdpi/img_snow_kiss.png
new file mode 100644
index 000000000..fa83e00ff
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-hdpi/img_snowman.png
new file mode 100644
index 000000000..819165273
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..07ba0a9d2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_top.png
new file mode 100644
index 000000000..e138aa19c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..bfff80762
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_top.png
new file mode 100644
index 000000000..dd9bec5e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..c41d98f4d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_top.png
new file mode 100644
index 000000000..17d10b3b0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..942585c82
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_top.png
new file mode 100644
index 000000000..6d460e5aa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..7bc9ddc44
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_top.png
new file mode 100644
index 000000000..8002cc87d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..6ad4a466a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_top.png
new file mode 100644
index 000000000..4e6bac5f6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-hdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-hdpi/img_yeti.png
new file mode 100644
index 000000000..22933b8a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-hdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_finalscore.png
new file mode 100644
index 000000000..a959d68d2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_finish.png
new file mode 100644
index 000000000..09e8ba1c0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..a631e759e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..c2dfc4a37
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..628496cd5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..785639123
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..ea80bdad7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..736219133
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_1.png
new file mode 100644
index 000000000..eff580e3b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_2.png
new file mode 100644
index 000000000..5f294dab7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_3.png
new file mode 100644
index 000000000..c6d069f56
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_4.png
new file mode 100644
index 000000000..55469c861
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-mdpi/btn_pause.png
new file mode 100644
index 000000000..6e7d5cb9e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-mdpi/btn_pause_p.png
new file mode 100644
index 000000000..7f0d2ce0d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-mdpi/btn_play.png
new file mode 100644
index 000000000..4310df004
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-mdpi/btn_play_p.png
new file mode 100644
index 000000000..ab8bda70d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay.png
new file mode 100644
index 000000000..5a8589961
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..1157123ea
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar.png
new file mode 100644
index 000000000..765fd14cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..24b8550e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/ic_launcher.png b/rocketsleigh/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..69e443b8c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-mdpi/icn_achievements.png
new file mode 100644
index 000000000..9cd0d234d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-mdpi/icn_leaderboards.png
new file mode 100644
index 000000000..45c1b05d7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-mdpi/icn_play_again.png
new file mode 100644
index 000000000..008c72620
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-mdpi/icn_sign_in.png
new file mode 100644
index 000000000..f45c1e231
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_100.png b/rocketsleigh/src/main/res/drawable-mdpi/img_100.png
new file mode 100644
index 000000000..b3b3cd44b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_100_purp.png
new file mode 100644
index 000000000..e28a48a2e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-mdpi/img_2_bats.png
new file mode 100644
index 000000000..ff54eb95c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-mdpi/img_3_bats.png
new file mode 100644
index 000000000..0b68f0341
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-mdpi/img_4_bats.png
new file mode 100644
index 000000000..25cebbd6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_500.png b/rocketsleigh/src/main/res/drawable-mdpi/img_500.png
new file mode 100644
index 000000000..c67001df8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_500_purp.png
new file mode 100644
index 000000000..d66c73f82
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-mdpi/img_5_bats.png
new file mode 100644
index 000000000..b04db65d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-mdpi/img_bear_big.png
new file mode 100644
index 000000000..2e3d1e6b9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-mdpi/img_bear_little.png
new file mode 100644
index 000000000..ded4fe95d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_0.png
new file mode 100644
index 000000000..311b0c669
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..ee206e6ca
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_top.png
new file mode 100644
index 000000000..9e3380126
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..0911ba710
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_top.png
new file mode 100644
index 000000000..c6e0af550
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..53d7f6bf4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_top.png
new file mode 100644
index 000000000..8d8a50d6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..2b8b4f7f8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_top.png
new file mode 100644
index 000000000..cdb964e3c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_bare.png
new file mode 100644
index 000000000..75d41f026
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_buttons.png
new file mode 100644
index 000000000..ddc4325f8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..7f9c4cf8f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..8885f8457
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..397473c97
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..679c28b0c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_corn.png
new file mode 100644
index 000000000..edf5855ee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-mdpi/img_choco_fountn.png
new file mode 100644
index 000000000..490c76687
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..3d9dd0ab1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..018fe4ffc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..95ed9c08d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..ab7081654
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..099a9b19d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..3804a0999
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..d0dd88d13
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..803ee7ae8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-mdpi/img_ice_swan.png
new file mode 100644
index 000000000..8be813fdf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_0.png
new file mode 100644
index 000000000..e2a5a68ec
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_1.png
new file mode 100644
index 000000000..1ea0c6aa0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_2.png
new file mode 100644
index 000000000..fd566e38e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_3.png
new file mode 100644
index 000000000..be6daf42b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_drop.png
new file mode 100644
index 000000000..99bb07fa2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..0e5699132
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..ddb4dca4b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..950c2b09e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..23242ea3d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..93feb4503
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..913a72001
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..81dae4f76
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..e4b86c53d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..916f6716f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..4bb85f243
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..e85312fcb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..20a5077ba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..13bd6dac5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..e85312fcb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..9f82508ae
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..df53ef135
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..9cd18fb74
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..9f82508ae
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..9189723ad
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..54f0b4497
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..5b75c75a0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..9189723ad
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_0.png
new file mode 100644
index 000000000..23a8a668e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100.png
new file mode 100644
index 000000000..9f90b4975
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..7cc73c296
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25.png
new file mode 100644
index 000000000..9e5385f87
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..d928bdc9d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50.png
new file mode 100644
index 000000000..0b93f1997
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..f3eae37cb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75.png
new file mode 100644
index 000000000..0a15e8aa2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..5c4eba1aa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-mdpi/img_log_elf.png
new file mode 100644
index 000000000..342132040
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-mdpi/img_lollipops.png
new file mode 100644
index 000000000..38cb3a7a7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mammoth.png
new file mode 100644
index 000000000..e9702688f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..4532eb49c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..6262aae69
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_gondola.png
new file mode 100644
index 000000000..9352c495f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..a56ec59b3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..6417ef894
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-mdpi/img_owl.png
new file mode 100644
index 000000000..641092a18
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_0.png
new file mode 100644
index 000000000..bbcbaf299
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..2d2279c94
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_top.png
new file mode 100644
index 000000000..4ece83479
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..12a533abf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_top.png
new file mode 100644
index 000000000..04860098c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..9c2b726ec
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_top.png
new file mode 100644
index 000000000..0a306fb3e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..c977fcf9e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_top.png
new file mode 100644
index 000000000..14f7ae1ba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_bare.png
new file mode 100644
index 000000000..926140b74
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-mdpi/img_snow_kiss.png
new file mode 100644
index 000000000..e92d3b680
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-mdpi/img_snowman.png
new file mode 100644
index 000000000..c7ffcb248
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..54dbf6cce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_top.png
new file mode 100644
index 000000000..df735fc4f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..b14681d85
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_top.png
new file mode 100644
index 000000000..18d30cb28
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..ba75a9c18
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_top.png
new file mode 100644
index 000000000..7a1376dee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..f34240c8b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_top.png
new file mode 100644
index 000000000..7b0ca6d88
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..a231ec11b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_top.png
new file mode 100644
index 000000000..ebd22a953
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..1babeb341
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_top.png
new file mode 100644
index 000000000..7044f387a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-mdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-mdpi/img_yeti.png
new file mode 100644
index 000000000..f9809560b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-mdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay.png
new file mode 100644
index 000000000..8b1837e72
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..40fad1371
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-nodpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finalscore.png
new file mode 100644
index 000000000..035542cab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finish.png
new file mode 100644
index 000000000..3cf89b931
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..24d4d1f62
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..62d403f39
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..2a0ece013
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..3796dfc21
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..0da07bc33
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..155fea53e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_1.png
new file mode 100644
index 000000000..94a6b49fa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_2.png
new file mode 100644
index 000000000..c61b7122f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_3.png
new file mode 100644
index 000000000..a89fb9f35
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_4.png
new file mode 100644
index 000000000..04a06faf0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause.png
new file mode 100644
index 000000000..fb29c5286
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause_p.png
new file mode 100644
index 000000000..484bff00c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play.png
new file mode 100644
index 000000000..fad1490fc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play_p.png
new file mode 100644
index 000000000..c274488fd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay.png
new file mode 100644
index 000000000..d893e1c01
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..f83bd253d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar.png
new file mode 100644
index 000000000..06123258c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..455c295ba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/ic_launcher.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/ic_launcher.png
new file mode 100644
index 000000000..91475edc4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/ic_launcher.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_achievements.png
new file mode 100644
index 000000000..90fc70c93
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_leaderboards.png
new file mode 100644
index 000000000..4b3f42a94
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_play_again.png
new file mode 100644
index 000000000..074d6df80
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_sign_in.png
new file mode 100644
index 000000000..f99274212
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100.png
new file mode 100644
index 000000000..be8cf112a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100_purp.png
new file mode 100644
index 000000000..77bd93b5b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_2_bats.png
new file mode 100644
index 000000000..10dc04f3b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_3_bats.png
new file mode 100644
index 000000000..c5b442ad7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_4_bats.png
new file mode 100644
index 000000000..089a73bee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500.png
new file mode 100644
index 000000000..b7c244f2c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500_purp.png
new file mode 100644
index 000000000..a6c34177e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_5_bats.png
new file mode 100644
index 000000000..03efed400
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_big.png
new file mode 100644
index 000000000..6eb586cde
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_little.png
new file mode 100644
index 000000000..c34cb8231
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_0.png
new file mode 100644
index 000000000..91cd2d321
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..38b7cce28
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_top.png
new file mode 100644
index 000000000..4f23167c0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..b18042dbd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_top.png
new file mode 100644
index 000000000..4a5d4b611
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..af631ea42
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_top.png
new file mode 100644
index 000000000..1d607a19c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..9f4898ac5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_top.png
new file mode 100644
index 000000000..d99cf85d9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_bare.png
new file mode 100644
index 000000000..e4f92b841
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_buttons.png
new file mode 100644
index 000000000..62d865642
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..bbd36223e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..cb8b69e4d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..f66c626bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..ac4f86bb7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_corn.png
new file mode 100644
index 000000000..8ffceb9b6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choco_fountn.png
new file mode 100644
index 000000000..8caed7763
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choo_choo_train.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choo_choo_train.png
new file mode 100644
index 000000000..d3dc4c252
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_choo_choo_train.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..37293ed60
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..166d5587b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..a84b4c654
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..91e50f92a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..9af65f67f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..f5ef06a37
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..414a804b0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..dc2ecaaf1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_ice_swan.png
new file mode 100644
index 000000000..ef6b62323
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_0.png
new file mode 100644
index 000000000..31e901ca4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_1.png
new file mode 100644
index 000000000..c3d5614cb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_2.png
new file mode 100644
index 000000000..bee32396f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_3.png
new file mode 100644
index 000000000..09a37e217
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_drop.png
new file mode 100644
index 000000000..070788f86
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..d66406b7c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..53eb39c50
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..12c727878
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..22ea563c1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..f446e2b34
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..9048fe96f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..f1640e5b8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..406997fee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..d3165c6b5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..7e501300b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn.png
new file mode 100644
index 000000000..e035f691d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..d1407d3d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..eacdc3cb1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..0b4f2f838
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..d1407d3d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke.png
new file mode 100644
index 000000000..e42076cd0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..a7e3102bb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..a6b824031
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..8d336ebba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..a7e3102bb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust.png
new file mode 100644
index 000000000..c03e54c63
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..09e58e545
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..f5ef9ced2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..f512782d9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..09e58e545
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_0.png
new file mode 100644
index 000000000..29bce02be
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100.png
new file mode 100644
index 000000000..f80c432bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..6f3ab2714
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25.png
new file mode 100644
index 000000000..a0ba066b8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..a2641e2c3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50.png
new file mode 100644
index 000000000..774b98f02
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..e79833284
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75.png
new file mode 100644
index 000000000..bd27dcd7a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..682c58b2e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_log_elf.png
new file mode 100644
index 000000000..295d60c29
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_lollipops.png
new file mode 100644
index 000000000..62e7fd4e8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mammoth.png
new file mode 100644
index 000000000..89be34586
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..156734c8f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..cd2374ba6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_gondola.png
new file mode 100644
index 000000000..a57e4f4d2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..81712647f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..54625871e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_owl.png
new file mode 100644
index 000000000..ee46e35a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_0.png
new file mode 100644
index 000000000..e3fcdb3c8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..a36d52966
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_top.png
new file mode 100644
index 000000000..bb94de9ee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..57b5f32a2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_top.png
new file mode 100644
index 000000000..f5f7f07b7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..56633304b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_top.png
new file mode 100644
index 000000000..0f7876d40
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..1713d834b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_top.png
new file mode 100644
index 000000000..8446e276a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_bare.png
new file mode 100644
index 000000000..220907a8e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snow_kiss.png
new file mode 100644
index 000000000..3174e77f2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snowman.png
new file mode 100644
index 000000000..9e0d329cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..4dc68e712
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_top.png
new file mode 100644
index 000000000..fe6f066e1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..47f89aead
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_top.png
new file mode 100644
index 000000000..eb2567bcb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..12f9e0855
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_top.png
new file mode 100644
index 000000000..b15b93a9e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..a301da899
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_top.png
new file mode 100644
index 000000000..9523e83e4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..ab38eb043
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_top.png
new file mode 100644
index 000000000..bc8e21d0e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..b59d4dcce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_top.png
new file mode 100644
index 000000000..15e6f0645
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_yeti.png
new file mode 100644
index 000000000..ae8ae078c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-hdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finalscore.png
new file mode 100644
index 000000000..035542cab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finish.png
new file mode 100644
index 000000000..ce06362cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..6462adf2f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..31240a2cd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..e5b76b9a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..d5d9470e4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..043028285
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..3fe95afa9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_1.png
new file mode 100644
index 000000000..4a5b1ef58
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_2.png
new file mode 100644
index 000000000..d9efe1e90
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_3.png
new file mode 100644
index 000000000..d9ae2e3d6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_4.png
new file mode 100644
index 000000000..304e1b948
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause.png
new file mode 100644
index 000000000..210c3d215
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause_p.png
new file mode 100644
index 000000000..b762fbd0f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play.png
new file mode 100644
index 000000000..58bac2d7f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play_p.png
new file mode 100644
index 000000000..c4cb6063f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay.png
new file mode 100644
index 000000000..a270085d8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..fb5054137
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar.png
new file mode 100644
index 000000000..c071816e2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..11bcdee08
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_achievements.png
new file mode 100644
index 000000000..ad6603ef7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_leaderboards.png
new file mode 100644
index 000000000..e7c070d16
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_play_again.png
new file mode 100644
index 000000000..d34491da2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_sign_in.png
new file mode 100644
index 000000000..80282d99e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100.png
new file mode 100644
index 000000000..cac14de25
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100_purp.png
new file mode 100644
index 000000000..47e4a7025
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_2_bats.png
new file mode 100644
index 000000000..b3ad159b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_3_bats.png
new file mode 100644
index 000000000..cca67a6bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_4_bats.png
new file mode 100644
index 000000000..be7fc1334
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500.png
new file mode 100644
index 000000000..94d7a56e6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500_purp.png
new file mode 100644
index 000000000..e97271cd8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_5_bats.png
new file mode 100644
index 000000000..da25b4eb4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_big.png
new file mode 100644
index 000000000..1c021b4b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_little.png
new file mode 100644
index 000000000..2664b21a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_0.png
new file mode 100644
index 000000000..a14a11fc6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..00b51b59c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_top.png
new file mode 100644
index 000000000..9d00931a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..47e44a5bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_top.png
new file mode 100644
index 000000000..104a6d867
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..48bb1b0f5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_top.png
new file mode 100644
index 000000000..6e21c79e8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..1b7a8700d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_top.png
new file mode 100644
index 000000000..83e2bd9a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_bare.png
new file mode 100644
index 000000000..c85e8a6ec
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_buttons.png
new file mode 100644
index 000000000..4b6fab13a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..3b9b67ba6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..c7750fe5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..0823e97fb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..6e08ca556
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_corn.png
new file mode 100644
index 000000000..6776b2fe6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_choco_fountn.png
new file mode 100644
index 000000000..ecb0e526f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..e025841bd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..e3ef16611
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..5673d046f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..9cd6d997f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..1244b9788
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..58f3e0463
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..41f7b528d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..b66c1a2eb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_ice_swan.png
new file mode 100644
index 000000000..9c93af32d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_0.png
new file mode 100644
index 000000000..dcc43f68f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_1.png
new file mode 100644
index 000000000..64c61ae5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_2.png
new file mode 100644
index 000000000..8a8d1555d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_3.png
new file mode 100644
index 000000000..a106a6a3c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_drop.png
new file mode 100644
index 000000000..1f92bc58f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..0ba0f3af1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..a8cbcff64
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..d3f8e032b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..b069fb847
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..ca8882f12
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..1a75dbafd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..3b8209293
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..ab6fe7ed2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..f8ca2bde5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..90d43e3ce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..bed0515db
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..5f4ae386c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..8c36fbb2d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..a4fa95ae0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..b3819e84f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..c43deec00
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_0.png
new file mode 100644
index 000000000..7d9313e0d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100.png
new file mode 100644
index 000000000..6e57a9785
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..b2c70547c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25.png
new file mode 100644
index 000000000..a9aba711b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..75249db3d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50.png
new file mode 100644
index 000000000..2bde83dda
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..b47a167e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75.png
new file mode 100644
index 000000000..d45b2dd6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..f8e72ea59
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_log_elf.png
new file mode 100644
index 000000000..ca8380d45
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_lollipops.png
new file mode 100644
index 000000000..6a1616a57
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mammoth.png
new file mode 100644
index 000000000..ea1687a1a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..acbed246e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..8e53450fd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_gondola.png
new file mode 100644
index 000000000..0ffa511df
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..10f3665bd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..0bc1d0ad1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_owl.png
new file mode 100644
index 000000000..94ebd8c25
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_0.png
new file mode 100644
index 000000000..8c471c6a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..d4946e7bc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_top.png
new file mode 100644
index 000000000..5d5014a9f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..823b6b563
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_top.png
new file mode 100644
index 000000000..4be71dc95
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..a92af5b4f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_top.png
new file mode 100644
index 000000000..d539c94fe
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..c7c22fff4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_top.png
new file mode 100644
index 000000000..470d1478b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_bare.png
new file mode 100644
index 000000000..f98e4ecb7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snow_kiss.png
new file mode 100644
index 000000000..2ca2238e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snowman.png
new file mode 100644
index 000000000..1cbe92892
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..fe876a23f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_top.png
new file mode 100644
index 000000000..04c7081a5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..544f96858
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_top.png
new file mode 100644
index 000000000..cf106200c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..b87e9ea03
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_top.png
new file mode 100644
index 000000000..80fb00cc8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..b57eff3f9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_top.png
new file mode 100644
index 000000000..7dfa16fce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..375f93d4a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_top.png
new file mode 100644
index 000000000..1f317c494
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..ebe011f10
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_top.png
new file mode 100644
index 000000000..a044737a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_yeti.png
new file mode 100644
index 000000000..b2e277d90
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-mdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finalscore.png
new file mode 100644
index 000000000..035542cab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finish.png
new file mode 100644
index 000000000..ce06362cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..24d4d1f62
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..62d403f39
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..2a0ece013
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..3796dfc21
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..0da07bc33
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..155fea53e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_1.png
new file mode 100644
index 000000000..94a6b49fa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_2.png
new file mode 100644
index 000000000..c61b7122f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_3.png
new file mode 100644
index 000000000..a89fb9f35
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_4.png
new file mode 100644
index 000000000..04a06faf0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause.png
new file mode 100644
index 000000000..210c3d215
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause_p.png
new file mode 100644
index 000000000..b762fbd0f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play.png
new file mode 100644
index 000000000..58bac2d7f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play_p.png
new file mode 100644
index 000000000..c4cb6063f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay.png
new file mode 100644
index 000000000..a270085d8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..fb5054137
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar.png
new file mode 100644
index 000000000..c071816e2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..11bcdee08
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_achievements.png
new file mode 100644
index 000000000..90fc70c93
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_leaderboards.png
new file mode 100644
index 000000000..4b3f42a94
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_play_again.png
new file mode 100644
index 000000000..074d6df80
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_sign_in.png
new file mode 100644
index 000000000..f99274212
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100.png
new file mode 100644
index 000000000..be8cf112a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100_purp.png
new file mode 100644
index 000000000..77bd93b5b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_2_bats.png
new file mode 100644
index 000000000..b3ad159b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_3_bats.png
new file mode 100644
index 000000000..cca67a6bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_4_bats.png
new file mode 100644
index 000000000..be7fc1334
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500.png
new file mode 100644
index 000000000..b7c244f2c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500_purp.png
new file mode 100644
index 000000000..a6c34177e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_5_bats.png
new file mode 100644
index 000000000..da25b4eb4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_big.png
new file mode 100644
index 000000000..1c021b4b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_little.png
new file mode 100644
index 000000000..2664b21a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_0.png
new file mode 100644
index 000000000..a14a11fc6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..00b51b59c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_top.png
new file mode 100644
index 000000000..9d00931a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..47e44a5bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_top.png
new file mode 100644
index 000000000..104a6d867
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..48bb1b0f5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_top.png
new file mode 100644
index 000000000..6e21c79e8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..1b7a8700d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_top.png
new file mode 100644
index 000000000..83e2bd9a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_bare.png
new file mode 100644
index 000000000..c85e8a6ec
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_buttons.png
new file mode 100644
index 000000000..4b6fab13a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..3b9b67ba6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..c7750fe5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..0823e97fb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..6e08ca556
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_corn.png
new file mode 100644
index 000000000..6776b2fe6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_choco_fountn.png
new file mode 100644
index 000000000..ecb0e526f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..37293ed60
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..166d5587b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..a84b4c654
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..9cd6d997f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..1244b9788
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..58f3e0463
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..41f7b528d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..b66c1a2eb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_ice_swan.png
new file mode 100644
index 000000000..9c93af32d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_0.png
new file mode 100644
index 000000000..dcc43f68f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_1.png
new file mode 100644
index 000000000..64c61ae5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_2.png
new file mode 100644
index 000000000..8a8d1555d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_3.png
new file mode 100644
index 000000000..a106a6a3c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_drop.png
new file mode 100644
index 000000000..1f92bc58f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..0ba0f3af1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..a8cbcff64
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..d3f8e032b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..b069fb847
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..ca8882f12
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..1a75dbafd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..3b8209293
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..ab6fe7ed2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..f8ca2bde5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..90d43e3ce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..bed0515db
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..5f4ae386c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..8c36fbb2d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..a4fa95ae0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..b3819e84f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..c43deec00
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_0.png
new file mode 100644
index 000000000..7d9313e0d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100.png
new file mode 100644
index 000000000..6e57a9785
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..b2c70547c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25.png
new file mode 100644
index 000000000..a9aba711b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..75249db3d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50.png
new file mode 100644
index 000000000..2bde83dda
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..b47a167e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75.png
new file mode 100644
index 000000000..d45b2dd6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..f8e72ea59
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_log_elf.png
new file mode 100644
index 000000000..ca8380d45
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_lollipops.png
new file mode 100644
index 000000000..6a1616a57
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mammoth.png
new file mode 100644
index 000000000..ea1687a1a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..acbed246e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..8e53450fd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_gondola.png
new file mode 100644
index 000000000..0ffa511df
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..10f3665bd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..0bc1d0ad1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_owl.png
new file mode 100644
index 000000000..94ebd8c25
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_0.png
new file mode 100644
index 000000000..8c471c6a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..d4946e7bc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_top.png
new file mode 100644
index 000000000..5d5014a9f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..823b6b563
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_top.png
new file mode 100644
index 000000000..4be71dc95
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..a92af5b4f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_top.png
new file mode 100644
index 000000000..d539c94fe
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..c7c22fff4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_top.png
new file mode 100644
index 000000000..470d1478b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_bare.png
new file mode 100644
index 000000000..f98e4ecb7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snow_kiss.png
new file mode 100644
index 000000000..2ca2238e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snowman.png
new file mode 100644
index 000000000..1cbe92892
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..fe876a23f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_top.png
new file mode 100644
index 000000000..04c7081a5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..544f96858
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_top.png
new file mode 100644
index 000000000..cf106200c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..b87e9ea03
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_top.png
new file mode 100644
index 000000000..80fb00cc8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..b57eff3f9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_top.png
new file mode 100644
index 000000000..7dfa16fce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..375f93d4a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_top.png
new file mode 100644
index 000000000..1f317c494
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..ebe011f10
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_top.png
new file mode 100644
index 000000000..a044737a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_yeti.png
new file mode 100644
index 000000000..b2e277d90
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-sw600dp-tvdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_finalscore.png
new file mode 100644
index 000000000..035542cab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_finish.png
new file mode 100644
index 000000000..ce06362cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..6462adf2f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..31240a2cd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..e5b76b9a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..d5d9470e4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..043028285
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..3fe95afa9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_1.png
new file mode 100644
index 000000000..4a5b1ef58
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_2.png
new file mode 100644
index 000000000..d9efe1e90
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_3.png
new file mode 100644
index 000000000..d9ae2e3d6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_4.png
new file mode 100644
index 000000000..304e1b948
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause.png
new file mode 100644
index 000000000..210c3d215
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause_p.png
new file mode 100644
index 000000000..b762fbd0f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-xhdpi/btn_play.png
new file mode 100644
index 000000000..58bac2d7f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-xhdpi/btn_play_p.png
new file mode 100644
index 000000000..c4cb6063f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay.png
new file mode 100644
index 000000000..a270085d8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..fb5054137
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar.png
new file mode 100644
index 000000000..c071816e2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..11bcdee08
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/ic_launcher.png b/rocketsleigh/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..df82a166d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-xhdpi/icn_achievements.png
new file mode 100644
index 000000000..ad6603ef7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-xhdpi/icn_leaderboards.png
new file mode 100644
index 000000000..e7c070d16
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-xhdpi/icn_play_again.png
new file mode 100644
index 000000000..d34491da2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-xhdpi/icn_sign_in.png
new file mode 100644
index 000000000..80282d99e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_100.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_100.png
new file mode 100644
index 000000000..cac14de25
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_100_purp.png
new file mode 100644
index 000000000..47e4a7025
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_2_bats.png
new file mode 100644
index 000000000..b3ad159b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_3_bats.png
new file mode 100644
index 000000000..cca67a6bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_4_bats.png
new file mode 100644
index 000000000..be7fc1334
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_500.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_500.png
new file mode 100644
index 000000000..94d7a56e6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_500_purp.png
new file mode 100644
index 000000000..e97271cd8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_5_bats.png
new file mode 100644
index 000000000..da25b4eb4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_big.png
new file mode 100644
index 000000000..1c021b4b1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_little.png
new file mode 100644
index 000000000..2664b21a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_0.png
new file mode 100644
index 000000000..a14a11fc6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..00b51b59c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_top.png
new file mode 100644
index 000000000..9d00931a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..47e44a5bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_top.png
new file mode 100644
index 000000000..104a6d867
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..48bb1b0f5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_top.png
new file mode 100644
index 000000000..6e21c79e8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..1b7a8700d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_top.png
new file mode 100644
index 000000000..83e2bd9a4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_bare.png
new file mode 100644
index 000000000..c85e8a6ec
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_buttons.png
new file mode 100644
index 000000000..4b6fab13a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..3b9b67ba6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..c7750fe5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..0823e97fb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..6e08ca556
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_corn.png
new file mode 100644
index 000000000..6776b2fe6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_choco_fountn.png
new file mode 100644
index 000000000..ecb0e526f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..e025841bd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..e3ef16611
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..5673d046f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..9cd6d997f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..1244b9788
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..58f3e0463
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..41f7b528d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..b66c1a2eb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_ice_swan.png
new file mode 100644
index 000000000..9c93af32d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_0.png
new file mode 100644
index 000000000..dcc43f68f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_1.png
new file mode 100644
index 000000000..64c61ae5d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_2.png
new file mode 100644
index 000000000..8a8d1555d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_3.png
new file mode 100644
index 000000000..a106a6a3c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_drop.png
new file mode 100644
index 000000000..1f92bc58f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..0ba0f3af1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..a8cbcff64
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..d3f8e032b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..b069fb847
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..ca8882f12
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..1a75dbafd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..3b8209293
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..ab6fe7ed2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..f8ca2bde5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..90d43e3ce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..bed0515db
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..5f4ae386c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..405318f6b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..8c36fbb2d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..a4fa95ae0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..1594ac7a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..b3819e84f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..c43deec00
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..9c6f4a11f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_0.png
new file mode 100644
index 000000000..7d9313e0d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100.png
new file mode 100644
index 000000000..6e57a9785
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..b2c70547c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25.png
new file mode 100644
index 000000000..a9aba711b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..75249db3d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50.png
new file mode 100644
index 000000000..2bde83dda
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..b47a167e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75.png
new file mode 100644
index 000000000..d45b2dd6f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..f8e72ea59
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_log_elf.png
new file mode 100644
index 000000000..ca8380d45
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_lollipops.png
new file mode 100644
index 000000000..6a1616a57
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mammoth.png
new file mode 100644
index 000000000..ea1687a1a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..acbed246e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..8e53450fd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_gondola.png
new file mode 100644
index 000000000..0ffa511df
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..10f3665bd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..0bc1d0ad1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_owl.png
new file mode 100644
index 000000000..94ebd8c25
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_0.png
new file mode 100644
index 000000000..8c471c6a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..d4946e7bc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_top.png
new file mode 100644
index 000000000..5d5014a9f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..823b6b563
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_top.png
new file mode 100644
index 000000000..4be71dc95
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..a92af5b4f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_top.png
new file mode 100644
index 000000000..d539c94fe
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..c7c22fff4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_top.png
new file mode 100644
index 000000000..470d1478b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_bare.png
new file mode 100644
index 000000000..f98e4ecb7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_ground_a_tile.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_ground_a_tile.png
new file mode 100644
index 000000000..961b639f5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_ground_a_tile.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_kiss.png
new file mode 100644
index 000000000..2ca2238e3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_snowman.png
new file mode 100644
index 000000000..1cbe92892
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..fe876a23f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_top.png
new file mode 100644
index 000000000..04c7081a5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..544f96858
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_top.png
new file mode 100644
index 000000000..cf106200c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..b87e9ea03
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_top.png
new file mode 100644
index 000000000..80fb00cc8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..b57eff3f9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_top.png
new file mode 100644
index 000000000..7dfa16fce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..375f93d4a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_top.png
new file mode 100644
index 000000000..1f317c494
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..ebe011f10
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_top.png
new file mode 100644
index 000000000..a044737a8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xhdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-xhdpi/img_yeti.png
new file mode 100644
index 000000000..b2e277d90
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xhdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finalscore.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finalscore.png
new file mode 100644
index 000000000..035542cab
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finalscore.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finish.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finish.png
new file mode 100644
index 000000000..3cf89b931
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_finish.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_1.png
new file mode 100644
index 000000000..24d4d1f62
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_2.png
new file mode 100644
index 000000000..62d403f39
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_3.png
new file mode 100644
index 000000000..2a0ece013
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_4.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_4.png
new file mode 100644
index 000000000..3796dfc21
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_5.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_5.png
new file mode 100644
index 000000000..0da07bc33
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_5.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_6.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_6.png
new file mode 100644
index 000000000..155fea53e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_jet_pack_6.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_1.png
new file mode 100644
index 000000000..94a6b49fa
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_2.png
new file mode 100644
index 000000000..c61b7122f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_3.png
new file mode 100644
index 000000000..a89fb9f35
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_4.png b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_4.png
new file mode 100644
index 000000000..04a06faf0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/bg_transition_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_achievements.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_achievements.png
new file mode 100644
index 000000000..57b1b4cd9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_leaderboards.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_leaderboards.png
new file mode 100644
index 000000000..2216a6569
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause.png
new file mode 100644
index 000000000..fb29c5286
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause_p.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause_p.png
new file mode 100644
index 000000000..484bff00c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_pause_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play.png
new file mode 100644
index 000000000..fad1490fc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_again.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_again.png
new file mode 100644
index 000000000..c64caad77
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_p.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_p.png
new file mode 100644
index 000000000..c274488fd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_play_p.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/btn_sign_in.png b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_sign_in.png
new file mode 100644
index 000000000..15bb5fc1b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/btn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay.png b/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay.png
new file mode 100644
index 000000000..d893e1c01
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay_pressed.png b/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..f83bd253d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/games_bigplay_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar.png b/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar.png
new file mode 100644
index 000000000..06123258c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png b/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..455c295ba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/ic_launcher.png b/rocketsleigh/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..91475edc4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/icn_achievements.png b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_achievements.png
new file mode 100644
index 000000000..90fc70c93
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_achievements.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/icn_leaderboards.png b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_leaderboards.png
new file mode 100644
index 000000000..4b3f42a94
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_leaderboards.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/icn_play_again.png b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_play_again.png
new file mode 100644
index 000000000..074d6df80
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_play_again.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/icn_sign_in.png b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_sign_in.png
new file mode 100644
index 000000000..f99274212
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/icn_sign_in.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_100.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_100.png
new file mode 100644
index 000000000..be8cf112a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_100_purp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_100_purp.png
new file mode 100644
index 000000000..77bd93b5b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_100_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_2_bats.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_2_bats.png
new file mode 100644
index 000000000..10dc04f3b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_2_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_3_bats.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_3_bats.png
new file mode 100644
index 000000000..c5b442ad7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_3_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_4_bats.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_4_bats.png
new file mode 100644
index 000000000..089a73bee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_4_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_500.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_500.png
new file mode 100644
index 000000000..b7c244f2c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_500.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_500_purp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_500_purp.png
new file mode 100644
index 000000000..a6c34177e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_500_purp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_5_bats.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_5_bats.png
new file mode 100644
index 000000000..03efed400
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_5_bats.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_big.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_big.png
new file mode 100644
index 000000000..6eb586cde
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_big.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_little.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_little.png
new file mode 100644
index 000000000..c34cb8231
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_bear_little.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_0.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_0.png
new file mode 100644
index 000000000..91cd2d321
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_bottom.png
new file mode 100644
index 000000000..38b7cce28
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_top.png
new file mode 100644
index 000000000..4f23167c0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_bottom.png
new file mode 100644
index 000000000..b18042dbd
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_top.png
new file mode 100644
index 000000000..4a5d4b611
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_bottom.png
new file mode 100644
index 000000000..af631ea42
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_top.png
new file mode 100644
index 000000000..1d607a19c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_bottom.png
new file mode 100644
index 000000000..9f4898ac5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_top.png
new file mode 100644
index 000000000..d99cf85d9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_bare.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_bare.png
new file mode 100644
index 000000000..e4f92b841
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_birch_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_buttons.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_buttons.png
new file mode 100644
index 000000000..62d865642
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_buttons.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_0.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_0.png
new file mode 100644
index 000000000..bbd36223e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_1.png
new file mode 100644
index 000000000..cb8b69e4d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_2.png
new file mode 100644
index 000000000..f66c626bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_3.png
new file mode 100644
index 000000000..ac4f86bb7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_cane_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_corn.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_corn.png
new file mode 100644
index 000000000..8ffceb9b6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_candy_corn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_choco_fountn.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_choco_fountn.png
new file mode 100644
index 000000000..8caed7763
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_choco_fountn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_choo_choo_train.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_choo_choo_train.png
new file mode 100644
index 000000000..d3dc4c252
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_choo_choo_train.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_elf.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_elf.png
new file mode 100644
index 000000000..37293ed60
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_blue.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_blue.png
new file mode 100644
index 000000000..166d5587b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_blue.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_white.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_white.png
new file mode 100644
index 000000000..a84b4c654
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_finalscore_snowground_white.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_blue_jp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_blue_jp.png
new file mode 100644
index 000000000..91e50f92a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_blue_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_green_jp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_green_jp.png
new file mode 100644
index 000000000..9af65f67f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_green_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_purple_jp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_purple_jp.png
new file mode 100644
index 000000000..f5ef06a37
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_purple_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_red_jp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_red_jp.png
new file mode 100644
index 000000000..414a804b0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_red_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_yellow_jp.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_yellow_jp.png
new file mode 100644
index 000000000..dc2ecaaf1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_gift_yellow_jp.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_ice_swan.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_ice_swan.png
new file mode 100644
index 000000000..ef6b62323
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_ice_swan.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_0.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_0.png
new file mode 100644
index 000000000..31e901ca4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_1.png
new file mode 100644
index 000000000..c3d5614cb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_2.png
new file mode 100644
index 000000000..bee32396f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_3.png
new file mode 100644
index 000000000..09a37e217
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_drop.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_drop.png
new file mode 100644
index 000000000..070788f86
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icecream_drop.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_1.png
new file mode 100644
index 000000000..d66406b7c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_2.png
new file mode 100644
index 000000000..53eb39c50
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_lrg_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_1.png
new file mode 100644
index 000000000..12c727878
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_2.png
new file mode 100644
index 000000000..22ea563c1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_3.png
new file mode 100644
index 000000000..f446e2b34
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_4.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_4.png
new file mode 100644
index 000000000..9048fe96f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_med_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_1.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_1.png
new file mode 100644
index 000000000..f1640e5b8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_1.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_2.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_2.png
new file mode 100644
index 000000000..406997fee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_2.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_3.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_3.png
new file mode 100644
index 000000000..d3165c6b5
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_3.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_4.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_4.png
new file mode 100644
index 000000000..7e501300b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_icicle_small_4.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn.png
new file mode 100644
index 000000000..e035f691d
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_100.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_100.png
new file mode 100644
index 000000000..d1407d3d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_25.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_25.png
new file mode 100644
index 000000000..eacdc3cb1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_50.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_50.png
new file mode 100644
index 000000000..0b4f2f838
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_75.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_75.png
new file mode 100644
index 000000000..d1407d3d0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_burn_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke.png
new file mode 100644
index 000000000..e42076cd0
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_100_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_100_hit.png
new file mode 100644
index 000000000..a7e3102bb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_25_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_25_hit.png
new file mode 100644
index 000000000..a6b824031
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_50_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_50_hit.png
new file mode 100644
index 000000000..8d336ebba
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_75_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_75_hit.png
new file mode 100644
index 000000000..a7e3102bb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_smoke_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust.png
new file mode 100644
index 000000000..c03e54c63
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_100.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_100.png
new file mode 100644
index 000000000..09e58e545
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_25.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_25.png
new file mode 100644
index 000000000..f5ef9ced2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_50.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_50.png
new file mode 100644
index 000000000..f512782d9
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_75.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_75.png
new file mode 100644
index 000000000..09e58e545
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jet_thrust_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_0.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_0.png
new file mode 100644
index 000000000..29bce02be
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100.png
new file mode 100644
index 000000000..f80c432bf
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100_hit.png
new file mode 100644
index 000000000..6f3ab2714
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_100_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25.png
new file mode 100644
index 000000000..a0ba066b8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25_hit.png
new file mode 100644
index 000000000..a2641e2c3
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_25_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50.png
new file mode 100644
index 000000000..774b98f02
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50_hit.png
new file mode 100644
index 000000000..e79833284
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_50_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75.png
new file mode 100644
index 000000000..bd27dcd7a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75_hit.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75_hit.png
new file mode 100644
index 000000000..682c58b2e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_jetelf_75_hit.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_log_elf.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_log_elf.png
new file mode 100644
index 000000000..295d60c29
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_log_elf.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_lollipops.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_lollipops.png
new file mode 100644
index 000000000..62e7fd4e8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_lollipops.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mammoth.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mammoth.png
new file mode 100644
index 000000000..89be34586
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mammoth.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_bottom.png
new file mode 100644
index 000000000..156734c8f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_top.png
new file mode 100644
index 000000000..cd2374ba6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_drop_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_gondola.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_gondola.png
new file mode 100644
index 000000000..a57e4f4d2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_gondola.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_bottom.png
new file mode 100644
index 000000000..81712647f
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_top.png
new file mode 100644
index 000000000..54625871e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_mint_stack_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_owl.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_owl.png
new file mode 100644
index 000000000..ee46e35a6
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_owl.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_0.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_0.png
new file mode 100644
index 000000000..e3fcdb3c8
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_0.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_bottom.png
new file mode 100644
index 000000000..a36d52966
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_top.png
new file mode 100644
index 000000000..bb94de9ee
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_bottom.png
new file mode 100644
index 000000000..57b5f32a2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_top.png
new file mode 100644
index 000000000..f5f7f07b7
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_bottom.png
new file mode 100644
index 000000000..56633304b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_top.png
new file mode 100644
index 000000000..0f7876d40
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_bottom.png
new file mode 100644
index 000000000..1713d834b
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_top.png
new file mode 100644
index 000000000..8446e276a
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_bare.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_bare.png
new file mode 100644
index 000000000..220907a8e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_pine_bare.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_ground_a_tile.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_ground_a_tile.png
new file mode 100644
index 000000000..804c8cfce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_ground_a_tile.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_kiss.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_kiss.png
new file mode 100644
index 000000000..3174e77f2
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snow_kiss.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_snowman.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snowman.png
new file mode 100644
index 000000000..9e0d329cc
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_snowman.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_bottom.png
new file mode 100644
index 000000000..4dc68e712
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_top.png
new file mode 100644
index 000000000..fe6f066e1
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_1_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_bottom.png
new file mode 100644
index 000000000..47f89aead
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_top.png
new file mode 100644
index 000000000..eb2567bcb
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_2_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_bottom.png
new file mode 100644
index 000000000..12f9e0855
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_top.png
new file mode 100644
index 000000000..b15b93a9e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_3_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_bottom.png
new file mode 100644
index 000000000..a301da899
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_top.png
new file mode 100644
index 000000000..9523e83e4
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_4_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_bottom.png
new file mode 100644
index 000000000..ab38eb043
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_top.png
new file mode 100644
index 000000000..bc8e21d0e
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_5_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_bottom.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_bottom.png
new file mode 100644
index 000000000..b59d4dcce
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_bottom.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_top.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_top.png
new file mode 100644
index 000000000..15e6f0645
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_tree_6_top.png differ
diff --git a/rocketsleigh/src/main/res/drawable-xxhdpi/img_yeti.png b/rocketsleigh/src/main/res/drawable-xxhdpi/img_yeti.png
new file mode 100644
index 000000000..ae8ae078c
Binary files /dev/null and b/rocketsleigh/src/main/res/drawable-xxhdpi/img_yeti.png differ
diff --git a/rocketsleigh/src/main/res/drawable/big_play_button.xml b/rocketsleigh/src/main/res/drawable/big_play_button.xml
new file mode 100644
index 000000000..92a3c3b3a
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/big_play_button.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/drawable/blue_box.xml b/rocketsleigh/src/main/res/drawable/blue_box.xml
new file mode 100644
index 000000000..43329e4b6
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/blue_box.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/drawable/cancelbar_pressed.xml b/rocketsleigh/src/main/res/drawable/cancelbar_pressed.xml
new file mode 100644
index 000000000..3d5a6870b
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/cancelbar_pressed.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/final_green_button.xml b/rocketsleigh/src/main/res/drawable/final_green_button.xml
new file mode 100644
index 000000000..0685f825c
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/final_green_button.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/final_orange_button.xml b/rocketsleigh/src/main/res/drawable/final_orange_button.xml
new file mode 100644
index 000000000..4416a4bf8
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/final_orange_button.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/final_red_button.xml b/rocketsleigh/src/main/res/drawable/final_red_button.xml
new file mode 100644
index 000000000..fe7023875
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/final_red_button.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/green_box.xml b/rocketsleigh/src/main/res/drawable/green_box.xml
new file mode 100644
index 000000000..063c42568
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/green_box.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/drawable/img_snow_ground_tiles.xml b/rocketsleigh/src/main/res/drawable/img_snow_ground_tiles.xml
new file mode 100644
index 000000000..0ccc741af
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/img_snow_ground_tiles.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/pause_button_jp.xml b/rocketsleigh/src/main/res/drawable/pause_button_jp.xml
new file mode 100644
index 000000000..5d9bb6f27
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/pause_button_jp.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/drawable/play_button_jp.xml b/rocketsleigh/src/main/res/drawable/play_button_jp.xml
new file mode 100644
index 000000000..9fa7ced6e
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/play_button_jp.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/drawable/plus100.xml b/rocketsleigh/src/main/res/drawable/plus100.xml
new file mode 100644
index 000000000..1a1e61503
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/plus100.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/plus500.xml b/rocketsleigh/src/main/res/drawable/plus500.xml
new file mode 100644
index 000000000..29444da5c
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/plus500.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/drawable/red_box.xml b/rocketsleigh/src/main/res/drawable/red_box.xml
new file mode 100644
index 000000000..3baf17691
--- /dev/null
+++ b/rocketsleigh/src/main/res/drawable/red_box.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/layout-sw600dp/activity_end_game.xml b/rocketsleigh/src/main/res/layout-sw600dp/activity_end_game.xml
new file mode 100644
index 000000000..1b1a3dda1
--- /dev/null
+++ b/rocketsleigh/src/main/res/layout-sw600dp/activity_end_game.xml
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/layout-sw600dp/activity_jet_pack_elf.xml b/rocketsleigh/src/main/res/layout-sw600dp/activity_jet_pack_elf.xml
new file mode 100644
index 000000000..466551d87
--- /dev/null
+++ b/rocketsleigh/src/main/res/layout-sw600dp/activity_jet_pack_elf.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/layout/activity_end_game.xml b/rocketsleigh/src/main/res/layout/activity_end_game.xml
new file mode 100644
index 000000000..45adb88a8
--- /dev/null
+++ b/rocketsleigh/src/main/res/layout/activity_end_game.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/layout/activity_jet_pack_elf.xml b/rocketsleigh/src/main/res/layout/activity_jet_pack_elf.xml
new file mode 100644
index 000000000..694efc814
--- /dev/null
+++ b/rocketsleigh/src/main/res/layout/activity_jet_pack_elf.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/layout/obstacle_layout.xml b/rocketsleigh/src/main/res/layout/obstacle_layout.xml
new file mode 100644
index 000000000..553dd2f3a
--- /dev/null
+++ b/rocketsleigh/src/main/res/layout/obstacle_layout.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
diff --git a/rocketsleigh/src/main/res/raw/intro_wipe.mp4 b/rocketsleigh/src/main/res/raw/intro_wipe.mp4
new file mode 100644
index 000000000..39b75c7e9
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/intro_wipe.mp4 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_background.ogg b/rocketsleigh/src/main/res/raw/jp_background.ogg
new file mode 100644
index 000000000..4e80b4c7c
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_background.ogg differ
diff --git a/rocketsleigh/src/main/res/raw/jp_crash_1.mp3 b/rocketsleigh/src/main/res/raw/jp_crash_1.mp3
new file mode 100644
index 000000000..f83ecac16
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_crash_1.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_crash_2.mp3 b/rocketsleigh/src/main/res/raw/jp_crash_2.mp3
new file mode 100644
index 000000000..0a4adb8a6
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_crash_2.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_crash_3.mp3 b/rocketsleigh/src/main/res/raw/jp_crash_3.mp3
new file mode 100644
index 000000000..7e460295e
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_crash_3.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_game_over.mp3 b/rocketsleigh/src/main/res/raw/jp_game_over.mp3
new file mode 100644
index 000000000..6b2c19297
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_game_over.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_jet_thrust.mp3 b/rocketsleigh/src/main/res/raw/jp_jet_thrust.mp3
new file mode 100644
index 000000000..34556e31a
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_jet_thrust.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_level_up.mp3 b/rocketsleigh/src/main/res/raw/jp_level_up.mp3
new file mode 100644
index 000000000..3d0dd8a29
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_level_up.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_score_big.mp3 b/rocketsleigh/src/main/res/raw/jp_score_big.mp3
new file mode 100644
index 000000000..534d56721
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_score_big.mp3 differ
diff --git a/rocketsleigh/src/main/res/raw/jp_score_small.mp3 b/rocketsleigh/src/main/res/raw/jp_score_small.mp3
new file mode 100644
index 000000000..0f4e9beff
Binary files /dev/null and b/rocketsleigh/src/main/res/raw/jp_score_small.mp3 differ
diff --git a/rocketsleigh/src/main/res/values-w820dp/dimens.xml b/rocketsleigh/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 000000000..63fc81644
--- /dev/null
+++ b/rocketsleigh/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/rocketsleigh/src/main/res/values/colors.xml b/rocketsleigh/src/main/res/values/colors.xml
new file mode 100644
index 000000000..77bd3aff6
--- /dev/null
+++ b/rocketsleigh/src/main/res/values/colors.xml
@@ -0,0 +1,3 @@
+
+ #CCB2E3F9
+
diff --git a/rocketsleigh/src/main/res/values/dimens.xml b/rocketsleigh/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..158384458
--- /dev/null
+++ b/rocketsleigh/src/main/res/values/dimens.xml
@@ -0,0 +1,9 @@
+
+
+ 16dp
+ 16dp
+ 46dp
+ 48dp
+ 27dp
+
+
diff --git a/rocketsleigh/src/main/res/values/game_ids.xml b/rocketsleigh/src/main/res/values/game_ids.xml
new file mode 100644
index 000000000..7db0d8a58
--- /dev/null
+++ b/rocketsleigh/src/main/res/values/game_ids.xml
@@ -0,0 +1,10 @@
+
+
+ CgkIioKF2qwCEAIQFw
+ CgkIioKF2qwCEAIQGA
+ CgkIioKF2qwCEAIQGQ
+ CgkIioKF2qwCEAIQGg
+ CgkIioKF2qwCEAIQFQ
+ CgkIioKF2qwCEAIQFg
+ CgkIioKF2qwCEAIQHw
+
\ No newline at end of file
diff --git a/rocketsleigh/src/main/res/values/strings_analytics.xml b/rocketsleigh/src/main/res/values/strings_analytics.xml
new file mode 100644
index 000000000..b2e860b54
--- /dev/null
+++ b/rocketsleigh/src/main/res/values/strings_analytics.xml
@@ -0,0 +1,8 @@
+
+
+ Rocket Sleigh
+ Rocket Sleigh End Game
+ Level Cleared
+ Final Score
+ Collision
+
\ No newline at end of file
diff --git a/santa-tracker/build.gradle b/santa-tracker/build.gradle
new file mode 100644
index 000000000..432b7c530
--- /dev/null
+++ b/santa-tracker/build.gradle
@@ -0,0 +1,100 @@
+apply plugin: 'com.android.application'
+
+ext.densityList = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi']
+
+android {
+ compileSdkVersion 23
+ // Need tools version 23, only major versions are available on build server
+ buildToolsVersion rootProject.ext.tools
+
+ defaultConfig {
+ applicationId "com.google.android.apps.santatracker"
+ minSdkVersion 15
+ targetSdkVersion 23
+ versionCode 3000100 // We have XX.YY.ZZZ digits available
+ versionName "3.0.10"
+ }
+
+ buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+ versionNameSuffix "-debug"
+ // Enabling multidex support.
+ multiDexEnabled true
+ }
+
+ release {
+ minifyEnabled true
+ proguardFile 'proguard.txt'
+ }
+ }
+
+ splits {
+ density {
+ // TODO: re-enable when we can figure out how to include <=dpi, not just =dpi
+ // as this is preventing games from auto-downsampling bitmaps to prevent OOMs
+ enable false
+ reset()
+ include(*densityList)
+ compatibleScreens 'small', 'normal', 'large', 'xlarge'
+ }
+ }
+}
+// Apply signing configurations, keys and other non-version-controlled things
+if (file('../internal/santa-tracker.gradle').exists()) {
+ apply from: '../internal/santa-tracker.gradle'
+}
+
+// Generate unique versionCodes for each APK variant
+// XXYYYYYYY - XX is density, YYYYYYY is the "natural" version code
+// Any new variations get added to the front
+import com.android.build.OutputFile;
+android.applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ def densityFilter = output.getFilter(OutputFile.DENSITY)
+ int densityVersionCode = (densityList.indexOf(densityFilter) + 1) * 10000000
+ output.versionCodeOverride = variant.mergedFlavor.versionCode + densityVersionCode
+ }
+}
+
+// Support library aar dependencies are loaded from the SDK
+dependencies {
+ compile project(':village')
+ compile project(':common')
+ compile project(':CCL')
+ compile project(':third_party:ShowcaseView')
+ compile project(':dasherdancer')
+ compile project(':rocketsleigh')
+ compile project(':snowdown')
+ wearApp project(':wearable')
+
+ compile files('../third_party/jbox2d/jbox2d-library-2.2.1.1.jar')
+ compile files('../third_party/youtube/YouTubeAndroidPlayerApi.jar')
+
+ compile rootProject.ext.supportV4
+ compile rootProject.ext.design
+ compile rootProject.ext.appCompat
+ compile rootProject.ext.design
+ compile rootProject.ext.mediaRouter
+ compile rootProject.ext.cardView
+ compile rootProject.ext.recyclerView
+ compile rootProject.ext.leanback
+ compile rootProject.ext.multidex
+
+ compile rootProject.ext.glide
+ compile rootProject.ext.androidMapsUtils
+
+ compile rootProject.ext.playServicesAppindexing
+ compile rootProject.ext.playServicesBase
+ compile rootProject.ext.playServicesBasement
+ compile rootProject.ext.playServicesCast
+ compile rootProject.ext.playServicesGames
+ compile rootProject.ext.playServicesMaps
+ compile rootProject.ext.playServicesWearable
+
+ compile rootProject.ext.firebaseCore
+ compile rootProject.ext.firebaseAnalytics
+ compile rootProject.ext.firebaseConfig
+}
+
+apply plugin: 'com.google.gms.google-services'
diff --git a/santa-tracker/libs/YouTubeAndroidPlayerApi.jar b/santa-tracker/libs/YouTubeAndroidPlayerApi.jar
new file mode 100644
index 000000000..79ed6337b
Binary files /dev/null and b/santa-tracker/libs/YouTubeAndroidPlayerApi.jar differ
diff --git a/santa-tracker/libs/YouTubeAndroidPlayerApi.jar.LICENSE b/santa-tracker/libs/YouTubeAndroidPlayerApi.jar.LICENSE
new file mode 100644
index 000000000..885ad74a3
--- /dev/null
+++ b/santa-tracker/libs/YouTubeAndroidPlayerApi.jar.LICENSE
@@ -0,0 +1,3 @@
+Copyright 2012 Google, Inc. All rights reserved.
+
+YouTube Android Player API
diff --git a/santa-tracker/lint.xml b/santa-tracker/lint.xml
new file mode 100644
index 000000000..068d2ddc1
--- /dev/null
+++ b/santa-tracker/lint.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/proguard.txt b/santa-tracker/proguard.txt
new file mode 100644
index 000000000..1f7109056
--- /dev/null
+++ b/santa-tracker/proguard.txt
@@ -0,0 +1,108 @@
+# This is a configuration file for ProGuard.
+# http://proguard.sourceforge.net/index.html#manual/usage.html
+
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-verbose
+
+# Optimization is turned off by default. Dex does not like code run
+# through the ProGuard optimize and preverify steps (and performs some
+# of these optimizations on its own).
+-dontoptimize
+-dontpreverify
+# Note that if you want to enable optimization, you cannot just
+# include optimization flags in your own project configuration file;
+# instead you will need to point to the
+# "proguard-android-optimize.txt" file instead of this one from your
+# project.properties file.
+
+# SourceFile and LineNumberTable are required for useful stack traces
+-keepattributes *Annotation*,SourceFile,LineNumberTable
+
+-keep public class com.google.vending.licensing.ILicensingService
+-keep public class com.android.vending.licensing.ILicensingService
+
+# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
+-keepclasseswithmembernames class * {
+ native ;
+}
+
+# keep setters in Views so that animations can still work.
+# see http://proguard.sourceforge.net/manual/examples.html#beans
+-keepclassmembers public class * extends android.view.View {
+ void set*(***);
+ *** get*();
+}
+
+# We want to keep methods in Activity that could be used in the XML attribute onClick
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+}
+
+# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
+
+-keepclassmembers class **.R$* {
+ public static ;
+}
+
+# The support library contains references to newer platform versions.
+# Don't warn about those in case this app is linking against an older
+# platform version. We know about them, and they are safe.
+-dontwarn android.support.**
+
+# Keep for Cast, TODO: be more explicit about exact classes to keep
+#-keep class android.support.v7.** { *; }
+#-keep interface android.support.v7.** { *; }
+
+# Allow obfuscation of android.support.v7.internal.view.menu.**
+# to avoid problem on Samsung 4.2.2 devices with appcompat v21
+# see https://code.google.com/p/android/issues/detail?id=78377
+-keep class !android.support.v7.internal.view.menu.**,android.support.** {*;}
+-keep interface !android.support.v7.internal.view.menu.**,android.support.** {*;}
+
+-keep class android.support.v4.app.** { *; }
+-keep interface android.support.v4.app.** { *; }
+
+# Config for Google Play Services: http://developer.android.com/google/play-services/setup.html#Setup
+-keep class * extends java.util.ListResourceBundle {
+ protected Object[][] getContents();
+}
+
+-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
+ public static final *** NULL;
+}
+
+-keepnames @com.google.android.gms.common.annotation.KeepName class *
+-keepclassmembernames class * {
+ @ccom.google.android.gms.common.annotation.KeepName *;
+}
+
+-keepnames class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+
+-keep public class com.google.android.apps.santatracker.village.** { public *;}
+
+-dontwarn com.google.android.gms.**
+
+# Called by native code (in snowdown)
+-dontobfuscate
+-keep class com.google.fpl.** { *; }
+-keep class com.google.android.gms.games.** { *; }
+-keep class com.google.android.gms.nearby.** { *; }
+-keep class com.google.android.gms.common.** { *; }
+-keep class com.google.vr.cardboard.** { *; }
+-keep class com.google.vrtoolkit.cardboard.** { *; }
+-keep class org.libsdl.app.** { *; }
+-keepclassmembers class org.libsdl.app.SDLActivity {
+ public static ;
+}
+
diff --git a/santa-tracker/src/debug/res/drawable-hdpi/ic_launcher_santa.png b/santa-tracker/src/debug/res/drawable-hdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..2144e0ce3
Binary files /dev/null and b/santa-tracker/src/debug/res/drawable-hdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/debug/res/drawable-mdpi/ic_launcher_santa.png b/santa-tracker/src/debug/res/drawable-mdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..7cc9ddcfd
Binary files /dev/null and b/santa-tracker/src/debug/res/drawable-mdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/debug/res/drawable-xhdpi/ic_launcher_santa.png b/santa-tracker/src/debug/res/drawable-xhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..c079ecb79
Binary files /dev/null and b/santa-tracker/src/debug/res/drawable-xhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/debug/res/drawable-xxhdpi/ic_launcher_santa.png b/santa-tracker/src/debug/res/drawable-xxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..794b2b407
Binary files /dev/null and b/santa-tracker/src/debug/res/drawable-xxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/debug/res/drawable-xxxhdpi/ic_launcher_santa.png b/santa-tracker/src/debug/res/drawable-xxxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..0b6599217
Binary files /dev/null and b/santa-tracker/src/debug/res/drawable-xxxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/debug/res/menu/menu_startup.xml b/santa-tracker/src/debug/res/menu/menu_startup.xml
new file mode 100644
index 000000000..e3773aa82
--- /dev/null
+++ b/santa-tracker/src/debug/res/menu/menu_startup.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/debug/res/values/config-build.xml b/santa-tracker/src/debug/res/values/config-build.xml
new file mode 100644
index 000000000..0e68f1b62
--- /dev/null
+++ b/santa-tracker/src/debug/res/values/config-build.xml
@@ -0,0 +1,12 @@
+
+ android_dogfood
+
+
+ AIzaSyDLSm2Xe8nnq_b5d6fXwCgo-dwrl4w5CzY
+
+
+ http://next-santa-api.appspot.com/info?client=%1$s\u0026rand=%2$f\u0026routeOffset=%3$d\u0026streamOffset=%4$d\u0026tz=%5$d\u0026language=%6$s\u0026fingerprint=%7$s
+
+
+ FA3B9AAD
+
\ No newline at end of file
diff --git a/santa-tracker/src/debug/res/values/debug_settings.xml b/santa-tracker/src/debug/res/values/debug_settings.xml
new file mode 100644
index 000000000..f7ec98b67
--- /dev/null
+++ b/santa-tracker/src/debug/res/values/debug_settings.xml
@@ -0,0 +1,6 @@
+
+
+ false
+ true
+
+
diff --git a/santa-tracker/src/debug/res/values/donottranslate.xml b/santa-tracker/src/debug/res/values/donottranslate.xml
new file mode 100644
index 000000000..fc5ad8454
--- /dev/null
+++ b/santa-tracker/src/debug/res/values/donottranslate.xml
@@ -0,0 +1,7 @@
+
+ Santa Tracker Debug
+ -
+ ":"
+ 00
+
+
diff --git a/santa-tracker/src/debug/res/values/game_ids.xml b/santa-tracker/src/debug/res/values/game_ids.xml
new file mode 100644
index 000000000..707f9f3fa
--- /dev/null
+++ b/santa-tracker/src/debug/res/values/game_ids.xml
@@ -0,0 +1,7 @@
+
+
+ 956109243791
+
+ CgkIj4u15OkbEAIQFA
+ CgkIj4u15OkbEAIQFQ
+
diff --git a/santa-tracker/src/debug/res/values/game_ids_jetpack.xml b/santa-tracker/src/debug/res/values/game_ids_jetpack.xml
new file mode 100644
index 000000000..86877500c
--- /dev/null
+++ b/santa-tracker/src/debug/res/values/game_ids_jetpack.xml
@@ -0,0 +1,19 @@
+
+
+ CgkIj4u15OkbEAIQAQ
+ CgkIj4u15OkbEAIQAg
+ CgkIj4u15OkbEAIQAw
+ CgkIj4u15OkbEAIQBA
+ CgkIj4u15OkbEAIQBQ
+ CgkIj4u15OkbEAIQBg
+ CgkIj4u15OkbEAIQBw
+ CgkIj4u15OkbEAIQCA
+ CgkIj4u15OkbEAIQCQ
+ CgkIj4u15OkbEAIQCg
+ CgkIj4u15OkbEAIQCw
+ CgkIj4u15OkbEAIQDA
+ CgkIj4u15OkbEAIQDQ
+ CgkIj4u15OkbEAIQDg
+ CgkIj4u15OkbEAIQDw
+ CgkIj4u15OkbEAIQEA
+
diff --git a/santa-tracker/src/debug/res/xml/config_analytics_global.xml b/santa-tracker/src/debug/res/xml/config_analytics_global.xml
new file mode 100644
index 000000000..9e98dea6b
--- /dev/null
+++ b/santa-tracker/src/debug/res/xml/config_analytics_global.xml
@@ -0,0 +1,6 @@
+
+
+ verbose
+ true
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/debug/res/xml/google_now_actions.xml b/santa-tracker/src/debug/res/xml/google_now_actions.xml
new file mode 100644
index 000000000..e631c88c5
--- /dev/null
+++ b/santa-tracker/src/debug/res/xml/google_now_actions.xml
@@ -0,0 +1,46 @@
+
+
+ com.google.android.apps.santatracker
+ 1
+ Santa Tracker
+
+ ShowAction
+ com.google.android.apps.santatracker.SHOW_SANTA
+ show $SantaSynonym:santa
+ show santa on Google Santa Tracker
+
+
+ $SantaSynonym
+ Santa
+ Santa Claus
+ Saint Nicholas
+ Father Christmas
+ Kris Kringle
+
+
+ ActivateAction
+ com.google.android.apps.santatracker.PLAY_GAME
+ play $SantaGame:game game
+ play $SantaGame:game
+ play Elf Jetpack game on Google Santa Tracker
+
+
+ $SantaGame
+ Elf Jetpack
+ Memory
+ Gumball
+ Rocket Sleigh
+ Dasher Dancer
+
+
+ ActivateAction
+ com.google.android.apps.santatracker.PLAY_RANDOM_GAME
+ play game
+ play a game
+ play games
+ play game on Google Santa Tracker
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/dogfood/res/drawable-hdpi/ic_launcher_santa.png b/santa-tracker/src/dogfood/res/drawable-hdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..992e71a90
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-hdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-hdpi/santatracker_logo_startup.png b/santa-tracker/src/dogfood/res/drawable-hdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..b30702f89
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-hdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-mdpi/ic_launcher_santa.png b/santa-tracker/src/dogfood/res/drawable-mdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..2f883e12d
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-mdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-mdpi/santatracker_logo_startup.png b/santa-tracker/src/dogfood/res/drawable-mdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..660d9f819
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-mdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-xhdpi/ic_launcher_santa.png b/santa-tracker/src/dogfood/res/drawable-xhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..8b92e7406
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-xhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-xhdpi/santatracker_logo_startup.png b/santa-tracker/src/dogfood/res/drawable-xhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..8bcc1ab28
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-xhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-xxhdpi/ic_launcher_santa.png b/santa-tracker/src/dogfood/res/drawable-xxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..7409533f1
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-xxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-xxhdpi/santatracker_logo_startup.png b/santa-tracker/src/dogfood/res/drawable-xxhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..078d88399
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-xxhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/dogfood/res/drawable-xxxhdpi/ic_launcher_santa.png b/santa-tracker/src/dogfood/res/drawable-xxxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..6197a4333
Binary files /dev/null and b/santa-tracker/src/dogfood/res/drawable-xxxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/dogfood/res/menu/menu_startup.xml b/santa-tracker/src/dogfood/res/menu/menu_startup.xml
new file mode 100644
index 000000000..d1c5dc818
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/menu/menu_startup.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/dogfood/res/values/config-build.xml b/santa-tracker/src/dogfood/res/values/config-build.xml
new file mode 100644
index 000000000..4318d66cc
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/values/config-build.xml
@@ -0,0 +1,11 @@
+
+ android_dogfood
+
+ AIzaSyBSCNzkbi_lUE1roN39aT_WmtJcpceJgb8
+
+
+ http://next-santa-api.appspot.com/info?client=%1$s\u0026rand=%2$f\u0026routeOffset=%3$d\u0026streamOffset=%4$d\u0026tz=%5$d\u0026language=%6$s\u0026fingerprint=%7$s
+
+
+ FA3B9AAD
+
\ No newline at end of file
diff --git a/santa-tracker/src/dogfood/res/values/debug_settings.xml b/santa-tracker/src/dogfood/res/values/debug_settings.xml
new file mode 100644
index 000000000..316ce44b1
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/values/debug_settings.xml
@@ -0,0 +1,6 @@
+
+
+ false
+ false
+
+
diff --git a/santa-tracker/src/dogfood/res/values/donottranslate.xml b/santa-tracker/src/dogfood/res/values/donottranslate.xml
new file mode 100644
index 000000000..e8e98bd74
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/values/donottranslate.xml
@@ -0,0 +1,6 @@
+
+ -
+ ":"
+ 00
+
+
diff --git a/santa-tracker/src/dogfood/res/values/game_ids.xml b/santa-tracker/src/dogfood/res/values/game_ids.xml
new file mode 100644
index 000000000..85cf954b5
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/values/game_ids.xml
@@ -0,0 +1,6 @@
+
+
+ 80719462666
+ CgkIioKF2qwCEAIQEw
+ CgkIioKF2qwCEAIQFA
+
diff --git a/santa-tracker/src/dogfood/res/values/game_ids_jetpack.xml b/santa-tracker/src/dogfood/res/values/game_ids_jetpack.xml
new file mode 100644
index 000000000..a7a4bb567
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/values/game_ids_jetpack.xml
@@ -0,0 +1,21 @@
+
+
+
+CgkIioKF2qwCEAIQAg
+CgkIioKF2qwCEAIQAw
+CgkIioKF2qwCEAIQBA
+CgkIioKF2qwCEAIQBg
+CgkIioKF2qwCEAIQBw
+CgkIioKF2qwCEAIQCA
+CgkIioKF2qwCEAIQCQ
+CgkIioKF2qwCEAIQCg
+CgkIioKF2qwCEAIQCw
+CgkIioKF2qwCEAIQDA
+CgkIioKF2qwCEAIQDQ
+CgkIioKF2qwCEAIQDg
+CgkIioKF2qwCEAIQDw
+CgkIioKF2qwCEAIQEA
+CgkIioKF2qwCEAIQEQ
+CgkIioKF2qwCEAIQEg
+
+
diff --git a/santa-tracker/src/dogfood/res/xml/config_analytics_global.xml b/santa-tracker/src/dogfood/res/xml/config_analytics_global.xml
new file mode 100644
index 000000000..c14fbd6c0
--- /dev/null
+++ b/santa-tracker/src/dogfood/res/xml/config_analytics_global.xml
@@ -0,0 +1,6 @@
+
+
+ verbose
+ false
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/elffood/res/drawable-hdpi/ic_launcher_santa.png b/santa-tracker/src/elffood/res/drawable-hdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..992e71a90
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-hdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-hdpi/santatracker_logo_startup.png b/santa-tracker/src/elffood/res/drawable-hdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..b30702f89
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-hdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-mdpi/ic_launcher_santa.png b/santa-tracker/src/elffood/res/drawable-mdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..2f883e12d
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-mdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-mdpi/santatracker_logo_startup.png b/santa-tracker/src/elffood/res/drawable-mdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..660d9f819
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-mdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-xhdpi/ic_launcher_santa.png b/santa-tracker/src/elffood/res/drawable-xhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..8b92e7406
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-xhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-xhdpi/santatracker_logo_startup.png b/santa-tracker/src/elffood/res/drawable-xhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..8bcc1ab28
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-xhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-xxhdpi/ic_launcher_santa.png b/santa-tracker/src/elffood/res/drawable-xxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..7409533f1
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-xxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-xxhdpi/santatracker_logo_startup.png b/santa-tracker/src/elffood/res/drawable-xxhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..078d88399
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-xxhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/elffood/res/drawable-xxxhdpi/ic_launcher_santa.png b/santa-tracker/src/elffood/res/drawable-xxxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..6197a4333
Binary files /dev/null and b/santa-tracker/src/elffood/res/drawable-xxxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/elffood/res/menu/menu_startup.xml b/santa-tracker/src/elffood/res/menu/menu_startup.xml
new file mode 100644
index 000000000..17cac0438
--- /dev/null
+++ b/santa-tracker/src/elffood/res/menu/menu_startup.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/elffood/res/values/config-build.xml b/santa-tracker/src/elffood/res/values/config-build.xml
new file mode 100644
index 000000000..0e68f1b62
--- /dev/null
+++ b/santa-tracker/src/elffood/res/values/config-build.xml
@@ -0,0 +1,12 @@
+
+ android_dogfood
+
+
+ AIzaSyDLSm2Xe8nnq_b5d6fXwCgo-dwrl4w5CzY
+
+
+ http://next-santa-api.appspot.com/info?client=%1$s\u0026rand=%2$f\u0026routeOffset=%3$d\u0026streamOffset=%4$d\u0026tz=%5$d\u0026language=%6$s\u0026fingerprint=%7$s
+
+
+ FA3B9AAD
+
\ No newline at end of file
diff --git a/santa-tracker/src/elffood/res/values/debug_settings.xml b/santa-tracker/src/elffood/res/values/debug_settings.xml
new file mode 100644
index 000000000..f7ec98b67
--- /dev/null
+++ b/santa-tracker/src/elffood/res/values/debug_settings.xml
@@ -0,0 +1,6 @@
+
+
+ false
+ true
+
+
diff --git a/santa-tracker/src/elffood/res/values/donottranslate.xml b/santa-tracker/src/elffood/res/values/donottranslate.xml
new file mode 100644
index 000000000..fc5ad8454
--- /dev/null
+++ b/santa-tracker/src/elffood/res/values/donottranslate.xml
@@ -0,0 +1,7 @@
+
+ Santa Tracker Debug
+ -
+ ":"
+ 00
+
+
diff --git a/santa-tracker/src/elffood/res/values/game_ids.xml b/santa-tracker/src/elffood/res/values/game_ids.xml
new file mode 100644
index 000000000..707f9f3fa
--- /dev/null
+++ b/santa-tracker/src/elffood/res/values/game_ids.xml
@@ -0,0 +1,7 @@
+
+
+ 956109243791
+
+ CgkIj4u15OkbEAIQFA
+ CgkIj4u15OkbEAIQFQ
+
diff --git a/santa-tracker/src/elffood/res/values/game_ids_jetpack.xml b/santa-tracker/src/elffood/res/values/game_ids_jetpack.xml
new file mode 100644
index 000000000..86877500c
--- /dev/null
+++ b/santa-tracker/src/elffood/res/values/game_ids_jetpack.xml
@@ -0,0 +1,19 @@
+
+
+ CgkIj4u15OkbEAIQAQ
+ CgkIj4u15OkbEAIQAg
+ CgkIj4u15OkbEAIQAw
+ CgkIj4u15OkbEAIQBA
+ CgkIj4u15OkbEAIQBQ
+ CgkIj4u15OkbEAIQBg
+ CgkIj4u15OkbEAIQBw
+ CgkIj4u15OkbEAIQCA
+ CgkIj4u15OkbEAIQCQ
+ CgkIj4u15OkbEAIQCg
+ CgkIj4u15OkbEAIQCw
+ CgkIj4u15OkbEAIQDA
+ CgkIj4u15OkbEAIQDQ
+ CgkIj4u15OkbEAIQDg
+ CgkIj4u15OkbEAIQDw
+ CgkIj4u15OkbEAIQEA
+
diff --git a/santa-tracker/src/elffood/res/xml/config_analytics_global.xml b/santa-tracker/src/elffood/res/xml/config_analytics_global.xml
new file mode 100644
index 000000000..9e98dea6b
--- /dev/null
+++ b/santa-tracker/src/elffood/res/xml/config_analytics_global.xml
@@ -0,0 +1,6 @@
+
+
+ verbose
+ true
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/elffood/res/xml/google_now_actions.xml b/santa-tracker/src/elffood/res/xml/google_now_actions.xml
new file mode 100644
index 000000000..e631c88c5
--- /dev/null
+++ b/santa-tracker/src/elffood/res/xml/google_now_actions.xml
@@ -0,0 +1,46 @@
+
+
+ com.google.android.apps.santatracker
+ 1
+ Santa Tracker
+
+ ShowAction
+ com.google.android.apps.santatracker.SHOW_SANTA
+ show $SantaSynonym:santa
+ show santa on Google Santa Tracker
+
+
+ $SantaSynonym
+ Santa
+ Santa Claus
+ Saint Nicholas
+ Father Christmas
+ Kris Kringle
+
+
+ ActivateAction
+ com.google.android.apps.santatracker.PLAY_GAME
+ play $SantaGame:game game
+ play $SantaGame:game
+ play Elf Jetpack game on Google Santa Tracker
+
+
+ $SantaGame
+ Elf Jetpack
+ Memory
+ Gumball
+ Rocket Sleigh
+ Dasher Dancer
+
+
+ ActivateAction
+ com.google.android.apps.santatracker.PLAY_RANDOM_GAME
+ play game
+ play a game
+ play games
+ play game on Google Santa Tracker
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/AndroidManifest.xml b/santa-tracker/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f445b4e3d
--- /dev/null
+++ b/santa-tracker/src/main/AndroidManifest.xml
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/assets/Lobster-Regular.otf b/santa-tracker/src/main/assets/Lobster-Regular.otf
new file mode 100644
index 000000000..fbf991a66
Binary files /dev/null and b/santa-tracker/src/main/assets/Lobster-Regular.otf differ
diff --git a/santa-tracker/src/main/assets/Roboto-Light.ttf b/santa-tracker/src/main/assets/Roboto-Light.ttf
new file mode 100644
index 000000000..13bf13af0
Binary files /dev/null and b/santa-tracker/src/main/assets/Roboto-Light.ttf differ
diff --git a/santa-tracker/src/main/assets/Roboto-Regular.ttf b/santa-tracker/src/main/assets/Roboto-Regular.ttf
new file mode 100644
index 000000000..0ba95c98c
Binary files /dev/null and b/santa-tracker/src/main/assets/Roboto-Regular.ttf differ
diff --git a/santa-tracker/src/main/assets/RobotoCondensed-Bold.ttf b/santa-tracker/src/main/assets/RobotoCondensed-Bold.ttf
new file mode 100644
index 000000000..f0fd409ef
Binary files /dev/null and b/santa-tracker/src/main/assets/RobotoCondensed-Bold.ttf differ
diff --git a/santa-tracker/src/main/assets/RobotoCondensed-Regular.ttf b/santa-tracker/src/main/assets/RobotoCondensed-Regular.ttf
new file mode 100644
index 000000000..713fd30c4
Binary files /dev/null and b/santa-tracker/src/main/assets/RobotoCondensed-Regular.ttf differ
diff --git a/santa-tracker/src/main/assets/jetpack_music.mp3 b/santa-tracker/src/main/assets/jetpack_music.mp3
new file mode 100644
index 000000000..bb2c2fb60
Binary files /dev/null and b/santa-tracker/src/main/assets/jetpack_music.mp3 differ
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/BitmapLoader.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/BitmapLoader.java
new file mode 100644
index 000000000..8c9de75f2
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/BitmapLoader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+public class BitmapLoader {
+
+ // Size is a multiplier
+ // Alpha is a value from 0 to 255
+ public static Bitmap loadImage(Resources resources, int resId, float size,
+ int alpha) {
+
+ Bitmap temp = BitmapFactory.decodeResource(resources, resId);
+ Bitmap rc = Bitmap.createScaledBitmap(temp,
+ (int) (temp.getWidth() * size),
+ (int) (temp.getHeight() * size), true);
+
+ Paint paint = new Paint();
+ paint.setAlpha(alpha);
+
+ temp = rc.copy(Bitmap.Config.ARGB_8888, true);
+ Canvas canvas = new Canvas(temp);
+ canvas.drawBitmap(rc, 0, 0, paint);
+
+ return temp;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/NotificationBroadcastReceiver.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/NotificationBroadcastReceiver.java
new file mode 100644
index 000000000..5fe997051
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/NotificationBroadcastReceiver.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 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;
+
+import com.google.android.apps.santatracker.common.NotificationConstants;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class NotificationBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "NotificationBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int type = intent.getIntExtra(NotificationConstants.KEY_NOTIFICATION_TYPE, -1);
+
+ switch (type) {
+ case NotificationConstants.NOTIFICATION_NEARBY:
+ SantaNotificationBuilder.CreateSantaNotification(context,
+ R.string.notification_nearby);
+ //requestWearNotification(context, R.string.notification_nearby);
+ break;
+ case NotificationConstants.NOTIFICATION_TAKEOFF:
+ SantaNotificationBuilder
+ .CreateSantaNotification(context, R.string.notification_takeoff);
+ //requestWearNotification(context, R.string.notification_takeoff);
+ break;
+ case NotificationConstants.NOTIFICATION_LOCATION:
+ String fact = intent.getStringExtra(NotificationConstants.KEY_LOCATION_FACT);
+ String location = intent.getStringExtra(NotificationConstants.KEY_LOCATION);
+ String photoUrl = intent.getStringExtra(NotificationConstants.KEY_LOCATION_PHOTO);
+ String mapUrl = intent.getStringExtra(NotificationConstants.KEY_LOCATION_MAP);
+ SantaNotificationBuilder
+ .CreateTriviaNotification(context, location, photoUrl, mapUrl, fact);
+ break;
+ case NotificationConstants.NOTIFICATION_FACT:
+ processFactNotification(context, intent);
+ break;
+ }
+ }
+
+ private void processFactNotification(Context context, Intent intent) {
+ final long finalArrival = intent.getLongExtra(NotificationConstants.KEY_FINAL_ARRIVAL, 0);
+ final long timestamp = intent.getLongExtra(NotificationConstants.KEY_TIMESTAMP, 0);
+
+ // Sanity check to make sure Santa is still travelling
+ if (timestamp > finalArrival) {
+ return;
+ }
+
+ final String didyouknow = intent.getStringExtra(NotificationConstants.KEY_FACT);
+ final String imageUrl = intent.getStringExtra(NotificationConstants.KEY_IMAGEURL);
+ final String status = intent.getStringExtra(NotificationConstants.KEY_STATUS);
+
+ String title;
+ String text;
+ if (didyouknow != null) {
+ title = context.getString(R.string.did_you_know);
+ text = didyouknow;
+ } else {
+ title = context.getString(R.string.update_from_santa);
+ text = status;
+ }
+ // Schedule the next notification
+ SantaNotificationBuilder.CreateInfoNotification(context, title, text, imageUrl);
+ }
+
+ private void requestWearNotification(Context context, int content) {
+ Intent wearIntent = new Intent(context, PhoneNotificationService.class);
+ wearIntent.setAction(NotificationConstants.ACTION_SEND);
+ wearIntent.putExtra(NotificationConstants.KEY_CONTENT,
+ context.getResources().getString(content));
+ context.startService(wearIntent);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/PhoneNotificationService.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/PhoneNotificationService.java
new file mode 100644
index 000000000..1771a80f9
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/PhoneNotificationService.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataEvent;
+import com.google.android.gms.wearable.DataEventBuffer;
+import com.google.android.gms.wearable.PutDataMapRequest;
+import com.google.android.gms.wearable.PutDataRequest;
+import com.google.android.gms.wearable.Wearable;
+import com.google.android.gms.wearable.WearableListenerService;
+import com.google.android.apps.santatracker.common.NotificationConstants;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.NotificationManagerCompat;
+import android.util.Log;
+
+import static com.google.android.gms.wearable.PutDataRequest.WEAR_URI_SCHEME;
+
+/**
+ * A {@link com.google.android.gms.wearable.WearableListenerService} that is invoked when the
+ * state of synced phone and wear notifications changes:
+ * - if a notification has been dismissed on the wearable, onDataChanged is called
+ *
+ * - if a notification should be shown on the wearable, so the DataApi should be updated
+ * - if a notification should be dismissed on the wearable, so the DataApi should be updated
+ */
+public class PhoneNotificationService extends WearableListenerService
+ implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
+ ResultCallback {
+
+ private final String TAG = "PhoneNotificationService";
+ private GoogleApiClient mGoogleApiClient;
+ private String action;
+ private Intent mIntent;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(Wearable.API)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ }
+
+ @Override
+ public void onDataChanged(DataEventBuffer dataEvents) {
+ for (DataEvent dataEvent : dataEvents) {
+ if (dataEvent.getType() == DataEvent.TYPE_DELETED) {
+ // A notification has been deleted on the watch and it modified the DataApi to
+ // notify us.
+ // Only one notification shown at a time, so dismiss it
+ NotificationManagerCompat.from(this).cancelAll();
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.e(TAG, "onStartCommand");
+ if (null != intent) {
+ mIntent = intent;
+ mGoogleApiClient.connect();
+ }
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+
+ @Override // ConnectionCallbacks
+ public void onConnected(Bundle bundle) {
+ SantaLog.d(TAG, "onConnected, action = " + mIntent.getAction());
+
+ if (mIntent.getAction().equals(NotificationConstants.ACTION_DISMISS)) {
+
+ final Uri dataItemUri =
+ new Uri.Builder().scheme(WEAR_URI_SCHEME).build();
+ SantaLog.d(TAG, "Deleting Uri: " + dataItemUri.toString());
+
+ Wearable.DataApi.deleteDataItems(
+ mGoogleApiClient, dataItemUri).setResultCallback(this);
+
+ } else if (mIntent.getAction().equals(NotificationConstants.ACTION_SEND)) {
+ requestWearableNotification(
+ mIntent.getStringExtra(NotificationConstants.KEY_CONTENT),
+ NotificationConstants.TAKEOFF_PATH);
+ }
+ }
+
+
+ /**
+ * Builds a DataItem that on the wearable will be interpreted as a request to show a
+ * notification. The result will be a notification that only shows up on the wearable.
+ */
+ private void requestWearableNotification(String content, String path) {
+ if (mGoogleApiClient.isConnected()) {
+ PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path);
+ putDataMapRequest.getDataMap().putString(NotificationConstants.KEY_CONTENT, content);
+
+ //Ensure data item is unique
+ putDataMapRequest.getDataMap().putLong("time", System.currentTimeMillis());
+ PutDataRequest request = putDataMapRequest.asPutDataRequest();
+ Wearable.DataApi.putDataItem(mGoogleApiClient, request)
+ .setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(DataApi.DataItemResult dataItemResult) {
+ if (!dataItemResult.getStatus().isSuccess()) {
+ Log.e(TAG, "buildWatchOnlyNotification(): Failed to set the data, "
+ + "status: " + dataItemResult.getStatus().getStatusCode());
+ } else {
+ SantaLog.e(TAG, "takeoff notification: " + dataItemResult.getStatus()
+ .getStatusCode());
+ }
+ mGoogleApiClient.disconnect();
+ }
+ });
+ } else {
+ Log.e(TAG, "Can't send data item: no Google API Client connection");
+ }
+ }
+
+ @Override // ConnectionCallbacks
+ public void onConnectionSuspended(int i) {
+ }
+
+ @Override // OnConnectionFailedListener
+ public void onConnectionFailed(ConnectionResult connectionResult) {
+ Log.e(TAG, "Failed to connect to the Google API client");
+ }
+
+ @Override // ResultCallback
+ public void onResult(DataApi.DeleteDataItemsResult deleteDataItemsResult) {
+ if (!deleteDataItemsResult.getStatus().isSuccess()) {
+ Log.e(TAG, "dismissWearableNotification(): failed to delete DataItem");
+ }
+ mGoogleApiClient.disconnect();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaApplication.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaApplication.java
new file mode 100644
index 000000000..c90b03310
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaApplication.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.content.Context;
+import android.support.multidex.MultiDexApplication;
+
+import com.google.android.apps.santatracker.cast.NotificationDataCastManager;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumerImpl;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/**
+ * The {@link android.app.Application} for this Santa application.
+ */
+public class SantaApplication extends MultiDexApplication {
+
+ private static final String TAG = "SantaApplication";
+
+ private static NotificationDataCastManager sCastManager = null;
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.Application#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Initialise the Google Analytics Tracker
+ AnalyticsManager.initializeAnalyticsTracker(this);
+ }
+
+ /**
+ * Returns the CastManager and adds tracking for connection events.
+ * Note that before calling this method you need to verify that Play Services is up to date
+ * using BaseCastManager.checkGooglePlayServices(..)
.
+ */
+ public static NotificationDataCastManager getCastManager(final Context context) {
+ //TODO: Support cast config from Santa API, including kill switch!
+
+ final FirebaseAnalytics measurement = FirebaseAnalytics.getInstance(context);
+
+ if (sCastManager == null) {
+ sCastManager = NotificationDataCastManager.initialize(context,
+ context.getString(R.string.cast_app_id),
+ context.getString(R.string.cast_namespace));
+ //sCastManager.enableFeatures(DataCastManager.FEATURE_DEBUGGING);
+
+ DataCastConsumerImpl consumer = new DataCastConsumerImpl() {
+ @Override
+ public void onConnected() {
+ SantaLog.d(TAG, "Cast device connected");
+
+ // App measurement event
+ MeasurementManager.recordCustomEvent(measurement,
+ context.getString(R.string.analytics_event_category_cast),
+ context.getString(R.string.analytics_cast_action_connection),
+ context.getString(R.string.analytics_cast_connected));
+
+ // [ANALYTICS EVENT]: Cast connected
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_cast,
+ R.string.analytics_cast_action_connection,
+ R.string.analytics_cast_connected);
+ }
+
+ @Override
+ public void onDisconnected() {
+ SantaLog.d(TAG, "Cast device disconnected");
+ // App measurement event
+ MeasurementManager.recordCustomEvent(measurement,
+ context.getString(R.string.analytics_event_category_cast),
+ context.getString(R.string.analytics_cast_action_connection),
+ context.getString(R.string.analytics_cast_disconnected));
+
+ // [ANALYTICS EVENT]: Cast disconnected
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_cast,
+ R.string.analytics_cast_action_connection,
+ R.string.analytics_cast_disconnected);
+ }
+ };
+ sCastManager.addDataCastConsumer(consumer);
+ }
+ return sCastManager;
+ }
+
+ public static void toogleCast(Context context, boolean turnOffCast) {
+ NotificationDataCastManager castManager = SantaApplication.getCastManager(context);
+ if (castManager == null) {
+ return;
+ }
+
+ if (turnOffCast) {
+ // Disable cast
+ castManager.disconnect();
+ castManager.stopCastDiscovery();
+ SantaLog.d(TAG, "Disabled cast.");
+ } else {
+ // Enable cast
+ castManager.startCastDiscovery();
+ SantaLog.d(TAG, "Enabled cast.");
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaNotificationBuilder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaNotificationBuilder.java
new file mode 100644
index 000000000..a72b595f2
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/SantaNotificationBuilder.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 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;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.google.android.apps.santatracker.common.NotificationConstants;
+import com.google.android.apps.santatracker.launch.StartupActivity;
+import com.google.android.apps.santatracker.util.ScheduleNotificationService;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.TaskStackBuilder;
+
+public class SantaNotificationBuilder {
+
+ // private static final String TAG = "SantaNotificationBuilder";
+
+ private static Notification GetNotification(Context c, int headline) {
+ Resources r = c.getResources();
+ Bitmap largeIcon = BitmapFactory.decodeResource(r,
+ R.drawable.santa_notification_background);
+ NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender()
+ .setHintHideIcon(false)
+ .setBackground(largeIcon);
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c)
+ .setSmallIcon(R.drawable.notification_small)
+ .setColor(c.getResources().getColor(R.color.brandSantaTracker))
+ .setAutoCancel(true)
+ .setContentTitle(r.getString(headline))
+ .setContentText(r.getString(R.string.track_santa))
+ .extend(wearableExtender);
+
+ Intent i = new Intent(c, StartupActivity.class);
+ i.putExtra(NotificationConstants.KEY_NOTIFICATION_TYPE, NotificationConstants.TAKEOFF_PATH);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(c);
+ stackBuilder.addParentStack(StartupActivity.class);
+ stackBuilder.addNextIntent(i);
+ PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mBuilder.setContentIntent(resultPendingIntent);
+
+ return mBuilder.build();
+ }
+
+ public static void CreateSantaNotification(Context c, int content) {
+ Notification n = GetNotification(c, content);
+
+ //Post the notification.
+ NotificationManagerCompat.from(c)
+ .notify(NotificationConstants.NOTIFICATION_ID, n);
+ }
+
+ public static void CreateTriviaNotification(Context c, String wheresSanta, String photoUrl,
+ String mapUrl, String fact) {
+
+ Resources r = c.getResources();
+ Bitmap largeIcon = BitmapFactory.decodeResource(r,
+ R.drawable.ic_launcher_santa);
+
+ //TODO download bitmaps
+ Bitmap photo = BitmapFactory.decodeResource(c.getResources(), R.drawable.location_photo);
+ Bitmap map = BitmapFactory.decodeResource(c.getResources(), R.drawable.staticmap);
+
+ // Make second page of notification (map with fact)
+ NotificationCompat.Builder page = new NotificationCompat.Builder(c)
+ .setSmallIcon(R.drawable.notification_small)
+ .setContentText(fact)
+ .extend(new NotificationCompat.WearableExtender().setBackground(map));
+
+ // Make main notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c)
+ .setSmallIcon(R.drawable.notification_small)
+ .setLargeIcon(largeIcon)
+ .setAutoCancel(true)
+ .setContentTitle(wheresSanta)
+ .extend(new NotificationCompat.WearableExtender()
+ .setBackground(photo)
+ .addPage(page.build()));
+
+ Intent i = new Intent(c, StartupActivity.class);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(c);
+ stackBuilder.addParentStack(StartupActivity.class);
+ stackBuilder.addNextIntent(i);
+ PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mBuilder.setContentIntent(resultPendingIntent);
+
+ Notification n = mBuilder.build();
+
+ NotificationManager mNotificationManager = (NotificationManager) c
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID, n);
+ }
+
+ public static void CreateInfoNotification(final Context c, final String title,
+ final String text,
+ final String photoUrl) {
+
+ if (photoUrl != null) {
+ Glide.with(c).load(photoUrl).asBitmap().into(new SimpleTarget() {
+ @Override
+ public void onResourceReady(Bitmap resource,
+ GlideAnimation super Bitmap> glideAnimation) {
+ CreateInfoNotificationWithBitmap(c, title, text, resource);
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ CreateInfoNotificationWithBitmap(c, title, text, null);
+ }
+ });
+ } else {
+ CreateInfoNotificationWithBitmap(c, title, text, null);
+ }
+ }
+
+ private static void CreateInfoNotificationWithBitmap(Context c, String title, String text,
+ Bitmap photo) {
+
+ Resources r = c.getResources();
+ Bitmap largeIcon = BitmapFactory.decodeResource(r,
+ R.drawable.santa_info_notification_background);
+ NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender()
+ .setHintHideIcon(false)
+ .setBackground(largeIcon);
+
+ if (photo != null) {
+ NotificationCompat.Builder page = new NotificationCompat.Builder(c)
+ .setSmallIcon(R.drawable.notification_small)
+ .setContentText(text)
+ .extend(new NotificationCompat.WearableExtender().setBackground(photo));
+ wearableExtender.addPage(page.build());
+ }
+
+ // Make main notification
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c)
+ .setSmallIcon(R.drawable.notification_small)
+ .setColor(c.getResources().getColor(R.color.brandSantaTrackerDark))
+ .setLargeIcon(largeIcon)
+ .setAutoCancel(true)
+ .setContentTitle(title)
+ .extend(wearableExtender);
+
+ if (photo == null) {
+ mBuilder.setContentText(text);
+ }
+
+ Intent i = new Intent(c, StartupActivity.class);
+ i.putExtra(NotificationConstants.KEY_NOTIFICATION_TYPE, NotificationConstants.KEY_LOCATION);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(c);
+ stackBuilder.addParentStack(StartupActivity.class);
+ stackBuilder.addNextIntent(i);
+ PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mBuilder.setContentIntent(resultPendingIntent);
+
+ Notification n = mBuilder.build();
+
+ NotificationManager mNotificationManager = (NotificationManager) c
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID, n);
+ }
+
+ /**
+ * Dismiss all notifications.
+ */
+ public static void DismissNotifications(Context c) {
+ NotificationManager mNotificationManager = (NotificationManager) c
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ mNotificationManager.cancelAll();
+
+ // Send a broadcast to the phone's service, so it will update the data API to
+ // let the watch know to cancel all its corresponding notifications.
+ Intent dismissWearableNotifications = new Intent(c, PhoneNotificationService.class);
+ dismissWearableNotifications.setAction(NotificationConstants.ACTION_DISMISS);
+ c.sendBroadcast(dismissWearableNotifications);
+
+ }
+
+ public static void ScheduleNotificationNotification(Context c){
+ Intent i = new Intent(c, ScheduleNotificationService.class);
+ c.startService(i);
+ }
+
+ public static void ScheduleSantaNotification(Context c, long timestamp, int notificationType) {
+
+ // Only schedule a notification if the time is in the future
+ if(timestamp < System.currentTimeMillis()){
+ return ;
+ }
+
+ AlarmManager alarm = (AlarmManager) c
+ .getSystemService(Context.ALARM_SERVICE);
+
+ Intent i = new Intent(c, NotificationBroadcastReceiver.class);
+ i.putExtra(NotificationConstants.KEY_NOTIFICATION_ID,
+ NotificationConstants.NOTIFICATION_ID);
+
+ // Type is "takeoff", "location", etc.
+ i.putExtra(NotificationConstants.KEY_NOTIFICATION_TYPE, notificationType);
+
+ // Generate unique pending intent
+ PendingIntent pi = PendingIntent.getBroadcast(c, notificationType, i, 0);
+
+ // Deliver next time the device is woken up
+ alarm.set(AlarmManager.RTC, timestamp, pi);
+
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastIntentReceiver.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastIntentReceiver.java
new file mode 100644
index 000000000..d2849b9fa
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastIntentReceiver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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.cast;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class DataCastIntentReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ NotificationDataCastManager castManager = NotificationDataCastManager.getInstance();
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ switch (action) {
+ case DataCastNotificationService.ACTION_STOP:
+ castManager.disconnect();
+ break;
+ }
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastNotificationService.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastNotificationService.java
new file mode 100644
index 000000000..50c189f3c
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/DataCastNotificationService.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 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.cast;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.launch.StartupActivity;
+import com.google.android.libraries.cast.companionlibrary.cast.DataCastManager;
+import com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumerImpl;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.NotificationCompat;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+/**
+ * A service to provide status bar Notifications when we are casting. For JB+ versions,
+ * notification
+ * area provides a play/pause toggle and an "x" button to disconnect but that for GB, we do not
+ * show that due to the framework limitations.
+ */
+public class DataCastNotificationService extends Service {
+
+ private static final String TAG = LogUtils.makeLogTag(DataCastNotificationService.class);
+
+
+ public static final String ACTION_STOP =
+ "com.google.android.apps.santatracker.cast.action.stop";
+
+ public static final String ACTION_VISIBILITY =
+ "com.google.android.apps.santatracker.cast.action.notificationvisibility";
+
+ private static final int NOTIFICATION_ID = 1;
+
+ public static final String NOTIFICATION_VISIBILITY = "visible";
+
+ private static Class> INTENT_ACTIVITY = StartupActivity.class;
+
+ private Notification mNotification;
+
+ private boolean mVisible;
+
+ private DataCastManager mCastManager;
+
+ private DataCastConsumerImpl mConsumer;
+
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mCastManager = NotificationDataCastManager.getInstance();
+ if (!mCastManager.isConnected() && !mCastManager.isConnecting()) {
+ mCastManager.reconnectSessionIfPossible();
+ }
+ mConsumer = new DataCastConsumerImpl() {
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ LOGD(TAG, "onApplicationDisconnected() was reached, stopping the notification"
+ + " service");
+ stopSelf();
+ }
+
+ @Override
+ public void onUiVisibilityChanged(boolean visible) {
+ mVisible = !visible;
+ if (mVisible && (mNotification != null)) {
+ startForeground(NOTIFICATION_ID, mNotification);
+ } else {
+ stopForeground(true);
+ }
+ }
+ };
+ mCastManager.addDataCastConsumer(mConsumer);
+
+ }
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LOGD(TAG, "onStartCommand");
+ if (intent != null) {
+
+ String action = intent.getAction();
+ if (ACTION_VISIBILITY.equals(action)) {
+ mVisible = intent.getBooleanExtra(NOTIFICATION_VISIBILITY, false);
+ LOGD(TAG, "onStartCommand(): Action: ACTION_VISIBILITY " + mVisible);
+ if (mNotification == null) {
+ setUpNotification();
+ }
+ if (mVisible && mNotification != null) {
+ startForeground(NOTIFICATION_ID, mNotification);
+ } else {
+ stopForeground(true);
+ }
+ } else {
+ LOGD(TAG, "onStartCommand(): Action: none");
+ }
+
+ } else {
+ LOGD(TAG, "onStartCommand(): Intent was null");
+ }
+
+ return Service.START_STICKY;
+ }
+
+ private void setUpNotification() {
+ build(R.string.app_name_santa, R.drawable.ic_launcher_santa);
+ }
+
+ /**
+ * Removes the existing notification.
+ */
+ private void removeNotification() {
+ ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).
+ cancel(NOTIFICATION_ID);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ removeNotification();
+ if (mCastManager != null && mConsumer != null) {
+ mCastManager.removeDataCastConsumer(mConsumer);
+ mCastManager = null;
+ }
+ }
+
+ /*
+ * Build the RemoteViews for the notification. We also need to add the appropriate "back stack"
+ * so when user goes into the CastPlayerActivity, she can have a meaningful "back" experience.
+ */
+ private void build(int titleId, int imageId) {
+
+ // Main Content PendingIntent
+ Intent contentIntent = new Intent(this, INTENT_ACTIVITY);
+
+ // Disconnect PendingIntent
+ Intent stopIntent = new Intent(ACTION_STOP);
+ stopIntent.setPackage(getPackageName());
+ PendingIntent stopPendingIntent = PendingIntent.getBroadcast(this, 0, stopIntent, 0);
+
+ // Media metadata
+ String castingTo = getResources().getString(R.string.ccl_casting_to_device,
+ mCastManager.getDeviceName());
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addParentStack(INTENT_ACTIVITY);
+ stackBuilder.addNextIntent(contentIntent);
+ PendingIntent contentPendingIntent =
+ stackBuilder.getPendingIntent(NOTIFICATION_ID, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder builder
+ = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.notification_small)
+ .setContentTitle(getResources().getString(titleId))
+ .setContentText(castingTo)
+ .setContentIntent(contentPendingIntent)
+ .setColor(ContextCompat.getColor(this, R.color.brandSantaTracker))
+ .addAction(R.drawable.ic_notification_disconnect_24dp,
+ getString(R.string.ccl_disconnect),
+ stopPendingIntent)
+ .setOngoing(true)
+ .setShowWhen(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+
+ mNotification = builder.build();
+
+ }
+
+ /*
+ * We try to disconnect application but even if that fails, we need to remove notification since
+ * that is the only way to get rid of it without going to the application
+ */
+ private void stopApplication() {
+ try {
+ LOGD(TAG, "Calling stopApplication");
+ mCastManager.disconnect();
+ } catch (Exception e) {
+ LOGE(TAG, "Failed to disconnect application", e);
+ }
+ stopSelf();
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/NotificationDataCastManager.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/NotificationDataCastManager.java
new file mode 100644
index 000000000..eb8153139
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/cast/NotificationDataCastManager.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.cast;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.libraries.cast.companionlibrary.cast.DataCastManager;
+import com.google.android.libraries.cast.companionlibrary.utils.LogUtils;
+
+import android.content.Context;
+import android.content.Intent;
+
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD;
+import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGE;
+
+public class NotificationDataCastManager extends DataCastManager {
+
+ private static final String TAG = LogUtils.makeLogTag(NotificationDataCastManager.class);
+
+
+ protected NotificationDataCastManager(Context context, String applicationId,
+ String... namespaces) {
+ super(context, applicationId, namespaces);
+ }
+
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String applicationStatus,
+ String sessionId, boolean wasLaunched) {
+ super.onApplicationConnected(appMetadata, applicationStatus, sessionId, wasLaunched);
+ LOGD(TAG, "onApplicationConnected");
+ startNotificationService();
+ }
+
+ @Override
+ public void onApplicationDisconnected(int errorCode) {
+ super.onApplicationDisconnected(errorCode);
+ LOGD(TAG, "onApplicationConnected");
+
+ stopNotificationService();
+ }
+
+ @Override
+ protected void onDeviceUnselected() {
+ super.onDeviceUnselected();
+ LOGD(TAG, "onDeviceUnselected");
+
+ stopNotificationService();
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ super.onConnectionFailed(result);
+ LOGD(TAG, "onConnectionFailed");
+
+ stopNotificationService();
+ }
+
+ private static NotificationDataCastManager sInstance;
+
+ public static synchronized NotificationDataCastManager initialize(Context context,
+ String applicationId, String... namespaces) {
+ if (sInstance == null) {
+ LOGD(TAG, "New instance of DataCastManager is created");
+ if (ConnectionResult.SUCCESS != GooglePlayServicesUtil
+ .isGooglePlayServicesAvailable(context)) {
+ String msg = "Couldn't find the appropriate version of Google Play Services";
+ LOGE(TAG, msg);
+ throw new RuntimeException(msg);
+ }
+ sInstance = new NotificationDataCastManager(context, applicationId, namespaces);
+ }
+ return sInstance;
+ }
+
+ public static NotificationDataCastManager getInstance() {
+ if (sInstance == null) {
+ String msg = "No DataCastManager instance was found, did you forget to initialize it?";
+ LOGE(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+ return sInstance;
+ }
+
+ /*
+ * Starts a service that can last beyond the lifetime of the application to provide
+ * notifications. The service brings itself down when needed. The service will be started only
+ * if the notification feature has been enabled during the initialization.
+ * @see {@link BaseCastManager#enableFeatures()}
+ */
+ private boolean startNotificationService() {
+ LOGD(TAG, "startNotificationService()");
+ Intent service = new Intent(mContext, DataCastNotificationService.class);
+ service.setPackage(mContext.getPackageName());
+ service.setAction(DataCastNotificationService.ACTION_VISIBILITY);
+ service.putExtra(DataCastNotificationService.NOTIFICATION_VISIBILITY, !mUiVisible);
+ return mContext.startService(service) != null;
+ }
+
+ private void stopNotificationService() {
+ LOGD(TAG, "stopNotificationService");
+
+ if (mContext != null) {
+ mContext.stopService(new Intent(mContext, DataCastNotificationService.class));
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/AllDestinationCursorLoader.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/AllDestinationCursorLoader.java
new file mode 100644
index 000000000..7e407bfd8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/AllDestinationCursorLoader.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.Context;
+import android.database.Cursor;
+
+
+/**
+ * Loader that returns a {@link DestinationDbHelper#getAllDestinationCursor()}
+ * Cursor that returns destinations past the timestamp.
+ */
+public class AllDestinationCursorLoader extends SqliteCursorLoader {
+
+ public AllDestinationCursorLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Cursor getCursor() {
+ return DestinationDbHelper.getInstance(getContext()).getAllDestinationCursor();
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/CursorHelper.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/CursorHelper.java
new file mode 100644
index 000000000..73904d41c
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/CursorHelper.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.database.Cursor;
+
+/**
+ * Encapsulates a cursor and provides some handy helper calls.
+ */
+public abstract class CursorHelper {
+
+ protected Cursor mCursor;
+
+ public CursorHelper(Cursor cursor) {
+ mCursor = cursor;
+ // reset position of cursor
+ mCursor.moveToFirst();
+ }
+
+ public boolean isLast() {
+ return mCursor.isLast();
+ }
+
+ public boolean hasNext() {
+ return !mCursor.isAfterLast();
+ }
+
+ public boolean isFirst() {
+ return mCursor.isFirst();
+ }
+ /**
+ * Returns the current object of the encapsulated cursor.
+ */
+ public T getCurrent() {
+ if (mCursor.isAfterLast()) {
+ return null; // at end - no more objects
+ } else {
+ return getParsedObject();
+ }
+ }
+
+ /**
+ * Returns the next object of the encapsulated cursor without moving
+ * it.
+ */
+ public T getPeekNext() {
+ mCursor.moveToNext();
+ T object = null;
+
+ if (mCursor.isAfterLast()) {
+ object = null; // at end - no more objects
+ } else {
+ object = getParsedObject();
+ }
+
+ mCursor.moveToPrevious();
+ return object;
+ }
+
+ /**
+ * Moves the cursor to the next position and returns its Object.
+ */
+ public T getNext() {
+ if (mCursor.move(1)) {
+ // moved cursor to next position, extract object
+ return getParsedObject();
+ } else {
+ // could not move the cursor, return null as error
+ return null;
+ }
+ }
+
+ public T getPrevious() {
+ // verify that there is a previous object
+ if (!mCursor.moveToPrevious()) {
+ return null;
+ }
+
+ T object = getParsedObject();
+ mCursor.moveToNext();
+ // return previous position
+ return object;
+
+ }
+
+ public boolean moveToNext() {
+ return mCursor.moveToNext();
+ }
+
+ protected abstract T getParsedObject();
+
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Destination.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Destination.java
new file mode 100644
index 000000000..380525bec
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Destination.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import com.google.android.gms.maps.model.LatLng;
+
+/**
+ * A destination that is on Santa's path.
+ *
+ * @author jfschmakeit
+ */
+public class Destination {
+
+ // delimiter between city,region and country when constructing name
+ private static final String NAME_DELIMITER = ", ";
+
+ public int id;
+ public String identifier;
+
+ public String city;
+ public String region;
+ public String country;
+
+ public long arrival;
+ public long departure;
+
+ public LatLng position;
+
+ public long presentsDelivered;
+ public long presentsDeliveredAtDestination;
+
+ // Details
+ public long timezone;
+ public long altitude;
+ public String photoString;
+ public String weatherString;
+ public String streetviewString;
+ public String gmmStreetviewString;
+
+ // Parsed data
+ public Weather weather = null;
+ public Photo[] photos = null;
+ public StreetView streetView = null;
+ public StreetView gmmStreetview = null;
+
+ /**
+ * Returns the concatenated name of this destination. City is required,
+ * region and country are optional.
+ */
+ public String getPrintName() {
+ StringBuilder s = new StringBuilder(city);
+
+ if (region != null) {
+ s.append(NAME_DELIMITER);
+ s.append(region);
+ }
+
+ if (country != null) {
+ s.append(NAME_DELIMITER);
+ s.append(country);
+ }
+
+ return s.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "Destination [id=" + id + ", identifier=" + identifier
+ + ", city=" + city + ", region=" + region + ", country="
+ + country + ", arrival=" + arrival + ", departure=" + departure
+ + ", position=" + position + ", presentsDelivered="
+ + presentsDelivered + ", presentsDeliveredAtDestination="
+ + presentsDeliveredAtDestination + "]";
+ }
+
+ public static class Weather {
+
+ public String url = null;
+ public double tempC = Integer.MAX_VALUE;
+ public double tempF = Integer.MAX_VALUE;
+ }
+
+ public static class Photo {
+ public String url = null;
+ public String attributionHTML= null;
+ }
+
+ public static class StreetView {
+
+ public String id = null;
+ public LatLng position = null;
+ public double heading = 0.0;
+ }
+}
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursor.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursor.java
new file mode 100644
index 000000000..ebb62dd64
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.database.Cursor;
+
+/**
+ * Encapsulates a cursor of Destinations.
+ *
+ * @author jfschmakeit
+ */
+public class DestinationCursor extends CursorHelper implements SantaDestinationContract {
+
+ public DestinationCursor(Cursor cursor) {
+ super(cursor);
+ }
+
+
+
+ /**
+ * Returns the {@link Destination} object of the position of the current
+ * cursor position. If the cursor points at an empty position, a
+ * {@link Destination} object with undefined values is returned. (The
+ * calling method should verify that the given Cursor is at a valid
+ * position.
+ */
+ protected Destination getParsedObject() {
+ return DestinationDbHelper.getCursorDestination(mCursor);
+ }
+
+ /**
+ * Returns true if there are no destinations left.
+ */
+ public boolean isFinished() {
+ return mCursor.isAfterLast();
+ }
+
+ /**
+ * Returns true if the Checks whether the departure time of the current
+ * position is in the past.
+ */
+ public boolean isInPast(long time) {
+ return time > mCursor.getLong(mCursor
+ .getColumnIndex(COLUMN_NAME_DEPARTURE));
+ }
+
+ /**
+ * Returns true if the given time is between the departure and arrival times
+ * of the current position
+ */
+ public boolean isVisiting(long time) {
+ if (mCursor.isAfterLast()) {
+ return false; // already finished
+ }
+
+ return time >= mCursor.getLong(mCursor
+ .getColumnIndex(COLUMN_NAME_ARRIVAL))
+ && time <= mCursor.getLong(mCursor
+ .getColumnIndex(COLUMN_NAME_DEPARTURE));
+ }
+
+ public boolean moveToNext() {
+ return mCursor.moveToNext();
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursorLoader.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursorLoader.java
new file mode 100644
index 000000000..6281ba887
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationCursorLoader.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.Context;
+import android.database.Cursor;
+
+/**
+ * Loader that returns a {@link DestinationDbHelper#getFollowingDestinations(long)}
+ * Cursor that returns destinations past the timestamp.
+ *
+ * @author jfschmakeit
+ */
+public class DestinationCursorLoader extends SqliteCursorLoader {
+
+ private long mTime = 0L;
+
+ public DestinationCursorLoader(Context context, long timestamp) {
+ super(context);
+ mTime = timestamp;
+ }
+
+ @Override
+ public Cursor getCursor() {
+ return DestinationDbHelper.getInstance(getContext())
+ .getFollowingDestinations(mTime);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationDbHelper.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationDbHelper.java
new file mode 100644
index 000000000..35f41cb47
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/DestinationDbHelper.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.apps.santatracker.service.APIProcessor;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+public class DestinationDbHelper extends SQLiteOpenHelper implements
+ SantaDestinationContract {
+
+ public static final int DATABASE_VERSION = 5;
+ public static final String DATABASE_NAME = "SantaTracker.db";
+
+ private static DestinationDbHelper sInstance = null;
+
+ private DestinationDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ /**
+ * Access to Singleton object of this class. Creates a new instance if it
+ * has not been created yet.
+ */
+ public static DestinationDbHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new DestinationDbHelper(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(SQL_CREATE_ENTRIES);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+
+ public void reinitialise() {
+ SQLiteDatabase db = getWritableDatabase();
+ // delete all entries
+ db.execSQL(SQL_DELETE_ENTRIES);
+
+ onCreate(db);
+
+ db.close();
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onUpgrade(db, oldVersion, newVersion);
+ }
+
+ public void emptyDestinationTable() {
+ getWritableDatabase().delete(TABLE_NAME, null, null);
+ }
+
+ public int getVersion() {
+ return getReadableDatabase().getVersion();
+ }
+
+ /**
+ * Expects a writeable {@link SQLiteDatabase} - used for batch commits.
+ */
+ public void insertDestination(SQLiteDatabase db, String id,
+ long arrivalTime, long departureTime, String city, String region,
+ String country, double locationLat, double locationLng,
+ long presentsDelivered, long presentsAtDestination, long timezone, long altitude,
+ String photos, String weather, String streetView, String gmmStreetView) {
+
+ ContentValues cv = new ContentValues();
+
+ cv.put(COLUMN_NAME_IDENTIFIER, id);
+
+ cv.put(COLUMN_NAME_ARRIVAL, arrivalTime);
+ cv.put(COLUMN_NAME_DEPARTURE, departureTime);
+
+ cv.put(COLUMN_NAME_CITY, city);
+ cv.put(COLUMN_NAME_REGION, region);
+ cv.put(COLUMN_NAME_COUNTRY, country);
+
+ cv.put(COLUMN_NAME_LAT, locationLat);
+ cv.put(COLUMN_NAME_LNG, locationLng);
+
+ cv.put(COLUMN_NAME_PRESENTSDELIVERED, presentsDelivered);
+ cv.put(COLUMN_NAME_PRESENTS_DESTINATION, presentsAtDestination);
+
+ cv.put(COLUMN_NAME_TIMEZONE, timezone);
+ cv.put(COLUMN_NAME_ALTITUDE, altitude);
+ cv.put(COLUMN_NAME_PHOTOS, photos);
+ cv.put(COLUMN_NAME_WEATHER, weather);
+ cv.put(COLUMN_NAME_STREETVIEW, streetView);
+ cv.put(COLUMN_NAME_GMMSTREETVIEW, gmmStreetView);
+
+ // TODO: verify whether the db parameter is needed - can we just get
+ // another writeable handle on the db (even if the transaction is
+ // started on a different one?)
+ db.insertOrThrow(TABLE_NAME, null, cv);
+ }
+
+ public Cursor getAllDestinationCursor() {
+ SQLiteDatabase db = getReadableDatabase();
+ return db.rawQuery("SELECT * FROM " + TABLE_NAME + " ORDER BY "
+ + COLUMN_NAME_ARRIVAL, null);
+ }
+
+ /**
+ * Returns a cursor for all destinations following (and including) the given
+ * timestamp.
+ */
+ public Cursor getFollowingDestinations(long time) {
+ SQLiteDatabase db = getReadableDatabase();
+ return db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_DEPARTURE + " >= " + Long.toString(time)
+ + " ORDER BY " + COLUMN_NAME_ARRIVAL, null);
+ }
+
+ public long getFirstDeparture() {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT " + COLUMN_NAME_DEPARTURE + " FROM "
+ + TABLE_NAME + " ORDER BY " + COLUMN_NAME_DEPARTURE
+ + " ASC LIMIT 1", null);
+ c.moveToFirst();
+ long l;
+ if (c.isAfterLast()) {
+ l = -1;
+ } else {
+ l = c.getLong(c.getColumnIndex(COLUMN_NAME_DEPARTURE));
+ }
+ c.close();
+ return l;
+ }
+
+ public long getLastArrival() {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT " + COLUMN_NAME_ARRIVAL + " FROM "
+ + TABLE_NAME + " ORDER BY " + COLUMN_NAME_ARRIVAL
+ + " DESC LIMIT 1", null);
+ c.moveToFirst();
+ long l;
+ if (c.isAfterLast()) {
+ l = -1;
+ } else {
+ l = c.getLong(c.getColumnIndex(COLUMN_NAME_ARRIVAL));
+ }
+ c.close();
+ return l;
+ }
+
+ public long getLastDeparture() {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT " + COLUMN_NAME_DEPARTURE + " FROM "
+ + TABLE_NAME + " ORDER BY " + COLUMN_NAME_DEPARTURE
+ + " DESC LIMIT 1", null);
+ c.moveToFirst();
+ long l;
+ if (c.isAfterLast()) {
+ l = -1;
+ } else {
+ l = c.getLong(c.getColumnIndex(COLUMN_NAME_DEPARTURE));
+ }
+ c.close();
+ return l;
+ }
+
+ /**
+ * Returns the destination with the given identifier.
+ */
+ public Destination getDestination(String identifier) {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_IDENTIFIER + " = " + identifier, null);
+ c.moveToFirst();
+ Destination d = getCursorDestination(c);
+ c.close();
+
+ return d;
+ }
+
+ /**
+ * Returns the destination with the given _id.
+ */
+ public Destination getDestination(int id) {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_ID + " = " + id, null);
+ c.moveToFirst();
+ Destination d = getCursorDestination(c);
+ c.close();
+
+ return d;
+ }
+
+ /**
+ * Helper method that converts the cursor to a destination object.
+ */
+ public static Destination getCursorDestination(Cursor mCursor) {
+
+ Destination d = new Destination();
+
+ d.id = mCursor.getInt(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_ID));
+ d.identifier = mCursor
+ .getString(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_IDENTIFIER));
+
+ d.city = mCursor.getString(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_CITY));
+ d.region = mCursor.getString(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_REGION));
+ d.country = mCursor.getString(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_COUNTRY));
+
+ d.arrival = mCursor.getLong(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_ARRIVAL));
+ d.departure = mCursor
+ .getLong(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_DEPARTURE));
+
+ double lat = mCursor.getDouble(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_LAT));
+ double lng = mCursor.getDouble(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_LNG));
+ d.position = new LatLng(lat, lng);
+
+ d.presentsDelivered = mCursor
+ .getLong(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_PRESENTSDELIVERED));
+ d.presentsDeliveredAtDestination = mCursor
+ .getLong(mCursor
+ .getColumnIndex(SantaDestinationContract.COLUMN_NAME_PRESENTS_DESTINATION));
+
+ d.timezone = mCursor
+ .getLong(mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_TIMEZONE));
+ d.altitude = mCursor
+ .getLong(mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_ALTITUDE));
+ d.photoString = mCursor
+ .getString(mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_PHOTOS));
+ d.weatherString = mCursor
+ .getString(mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_WEATHER));
+ d.streetviewString = mCursor
+ .getString(mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_STREETVIEW));
+
+ d.gmmStreetviewString = mCursor
+ .getString(
+ mCursor.getColumnIndex(SantaDestinationContract.COLUMN_NAME_GMMSTREETVIEW));
+
+ // Process the panoramio string if possible
+ d.photos = processPhoto(d.photoString);
+ d.weather = processWeather(d.weatherString);
+ d.streetView = processStreetView(d.streetviewString);
+ d.gmmStreetview = processStreetView(d.gmmStreetviewString);
+
+ return d;
+ }
+
+
+ private static Destination.Photo[] processPhoto(String s) {
+ if (s == null || s.isEmpty()) {
+ return null;
+ }
+
+ ArrayList list = new ArrayList(5);
+
+ try {
+ JSONArray array = new JSONArray(s);
+ for (int i = 0; i < array.length(); i++) {
+ JSONObject json = array.getJSONObject(i);
+ Destination.Photo photo= new Destination.Photo();
+ photo.url = json.getString(APIProcessor.FIELD_PHOTO_URL);
+ photo.attributionHTML= json.getString(APIProcessor.FIELD_PHOTO_ATTRIBUTIONHTML);
+
+ list.add(photo);
+ }
+
+ } catch (JSONException e) {
+ // ignore invalid values
+ }
+ return list.isEmpty() ? null : list.toArray(new Destination.Photo[list.size()]);
+
+ }
+
+ private static Destination.StreetView processStreetView(String s) {
+ if (s == null || s.isEmpty()) {
+ return null;
+ }
+
+ try {
+ Destination.StreetView streetView = new Destination.StreetView();
+ JSONObject json = new JSONObject(s);
+ streetView.id = json.getString(APIProcessor.FIELD_STREETVIEW_ID);
+ streetView.heading = json.getDouble(APIProcessor.FIELD_STREETVIEW_HEADING);
+
+ if(json.has(APIProcessor.FIELD_STREETVIEW_LATITUDE) &&
+ json.has(APIProcessor.FIELD_STREETVIEW_LONGITUDE)) {
+ double lat =json.getDouble(APIProcessor.FIELD_STREETVIEW_LATITUDE);
+ double lng = json.getDouble(APIProcessor.FIELD_STREETVIEW_LONGITUDE);
+ LatLng location = new LatLng(lat, lng);
+ streetView.position = location;
+ }
+ return streetView;
+ } catch (JSONException e) {
+ // ignore invalid values
+ }
+ return null;
+ }
+
+ private static Destination.Weather processWeather(String s) {
+ if (s == null || s.isEmpty()) {
+ return null;
+ }
+
+ try {
+ Destination.Weather weather = new Destination.Weather();
+ JSONObject json = new JSONObject(s);
+ weather.url = APIProcessor.getExistingJSONString(json, APIProcessor.FIELD_WEATHER_URL);
+ weather.tempC = APIProcessor
+ .getExistingJSONDouble(json, APIProcessor.FIELD_WEATHER_TEMPC);
+ weather.tempF = APIProcessor.getExistingJSONDouble(json,
+ APIProcessor.FIELD_WEATHER_TEMPF);
+
+ return weather;
+ } catch (JSONException e) {
+ // ignore invalid values
+ }
+ return null;
+ }
+
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/LegacyPrefrences.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/LegacyPrefrences.java
new file mode 100644
index 000000000..c6f1db6e0
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/LegacyPrefrences.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+/**
+ * Keeps track of unused preferences that can be removed.
+ *
+ * @see com.google.android.apps.santatracker.data.SantaPreferences#onUpgrade(int, int)
+ */
+public class LegacyPrefrences {
+
+ static class CastFlags_1 {
+
+ public static final String Airport = "Airport";
+ public static final String SantaCallVideo = "SantaCallVideo";
+ public static final String StreetView = "StreetView";
+ public static final String Factory = "Factory";
+ public static final String FerrisWheel = "FerrisWheel";
+ public static final String SnowdomeExperiment = "SnowdomeExperiment";
+ public static final String ChoirVideo = "ChoirVideo";
+ public static final String Commandcentre = "Commandcentre";
+ public static final String Mountain = "Mountain";
+ public static final String Playground = "Playground";
+ public static final String Rollercoaster = "Rollercoaster";
+ public static final String Windtunnel = "Windtunnel";
+ public static final String Workshop = "Workshop";
+ public static final String FinalPrepVideo = "FinalPrepVideo";
+ public static final String Tracker = "Tracker";
+ public static final String Village = "Village";
+ public static final String BriefingRoom = "BriefingRoom";
+
+
+ public static final String EnableAudio = "EnableAudio";
+
+ public static final String[] ALL_FLAGS = new String[]{Village, BriefingRoom, Airport,
+ Windtunnel, StreetView, Factory,
+ Rollercoaster, Commandcentre, SantaCallVideo, Mountain, Playground, FerrisWheel,
+ SnowdomeExperiment, ChoirVideo, Workshop, FinalPrepVideo, Tracker, EnableAudio};
+
+ }
+
+ public static final String PREF_EARTH = "PREF_EARTHDISABLED";
+ private static final String PREF_CASTROUTE = "PREF_CASTROUTE";
+
+ private static final String PREF_CASTFTU = "PREF_CASTFTU";
+ private static final String PREF_CASTTOUCHPAD = "PREF_CASTTOUCHPAD";
+
+ public static final String[] PREFERENCES = new String[] {PREF_EARTH, PREF_CASTFTU, PREF_CASTROUTE,
+ PREF_CASTTOUCHPAD};
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/PresentCounter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/PresentCounter.java
new file mode 100644
index 000000000..d9560a90b
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/PresentCounter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+public class PresentCounter {
+
+ private long mInitialPresents = 0L;
+ private long mPresentsDelivery = 0L;
+ private long mStartTime = 0L;
+ private double mDuration = 0L;
+
+ public void init(long initialPresents, long totalPresents,
+ long startTime, long endTime) {
+ this.mInitialPresents = initialPresents;
+
+ this.mPresentsDelivery = totalPresents - initialPresents;
+ this.mStartTime = startTime;
+ this.mDuration = endTime - startTime;
+ }
+
+ public long getPresents(long time) {
+ double progress = (double) (time - mStartTime) / mDuration;
+
+ if (progress < 0.0) {
+ // do not return negative presents if progress is incorrect
+ return mInitialPresents;
+ } else if (progress > 1.0) {
+ return mInitialPresents + mPresentsDelivery;
+ } else {
+ return Math.round(mInitialPresents + (mPresentsDelivery * progress));
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaDestinationContract.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaDestinationContract.java
new file mode 100644
index 000000000..40d3cf6c2
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaDestinationContract.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+public interface SantaDestinationContract {
+
+ public static final String TABLE_NAME = "destinations";
+
+ // Fields
+ public static final String COLUMN_NAME_ID = "_id"; // SQLite PK
+ public static final String COLUMN_NAME_IDENTIFIER = "identifier"; // Identifier
+ // of
+ // location
+
+ public static final String COLUMN_NAME_ARRIVAL = "arrival";
+ public static final String COLUMN_NAME_DEPARTURE = "departure";
+
+ public static final String COLUMN_NAME_CITY = "city";
+ public static final String COLUMN_NAME_REGION = "region";
+ public static final String COLUMN_NAME_COUNTRY = "country";
+
+ public static final String COLUMN_NAME_LAT = "lat";
+ public static final String COLUMN_NAME_LNG = "lng";
+
+ public static final String COLUMN_NAME_PRESENTSDELIVERED = "presentsdelivered";
+ public static final String COLUMN_NAME_PRESENTS_DESTINATION = "presentsdeliveredatdestination";
+
+ public static final String COLUMN_NAME_TIMEZONE = "timezone";
+ public static final String COLUMN_NAME_ALTITUDE = "altitude";
+ public static final String COLUMN_NAME_PHOTOS = "photos";
+ public static final String COLUMN_NAME_WEATHER = "weather";
+ public static final String COLUMN_NAME_STREETVIEW = "streetview";
+ public static final String COLUMN_NAME_GMMSTREETVIEW = "gmmstreetview";
+
+
+ // Data types
+ public static final String TEXT_TYPE = " TEXT";
+ public static final String INT_TYPE = " INTEGER";
+ public static final String REAL_TYPE = " REAL";
+ public static final String COMMA_SEP = ",";
+
+ // SQL statements
+ public static final String SQL_CREATE_ENTRIES = "CREATE TABLE "
+ + TABLE_NAME + " (" + COLUMN_NAME_ID + " INTEGER PRIMARY KEY,"
+
+ + COLUMN_NAME_IDENTIFIER + TEXT_TYPE + " UNIQUE " + COMMA_SEP
+
+ + COLUMN_NAME_ARRIVAL + INT_TYPE + COMMA_SEP
+ + COLUMN_NAME_DEPARTURE + INT_TYPE + COMMA_SEP
+
+ + COLUMN_NAME_CITY + TEXT_TYPE + COMMA_SEP + COLUMN_NAME_REGION
+ + TEXT_TYPE + COMMA_SEP + COLUMN_NAME_COUNTRY + TEXT_TYPE
+ + COMMA_SEP
+
+ + COLUMN_NAME_LAT + REAL_TYPE + COMMA_SEP + COLUMN_NAME_LNG
+ + REAL_TYPE + COMMA_SEP
+
+ + COLUMN_NAME_PRESENTSDELIVERED + INT_TYPE + COMMA_SEP
+ + COLUMN_NAME_PRESENTS_DESTINATION + INT_TYPE + COMMA_SEP
+
+ + COLUMN_NAME_TIMEZONE + INT_TYPE + COMMA_SEP
+ + COLUMN_NAME_ALTITUDE + INT_TYPE + COMMA_SEP
+ + COLUMN_NAME_PHOTOS + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_WEATHER + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_STREETVIEW + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_GMMSTREETVIEW + TEXT_TYPE
+ + " )";
+
+ public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS "
+ + TABLE_NAME;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaPreferences.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaPreferences.java
new file mode 100644
index 000000000..70b4432de
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaPreferences.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.util.Log;
+
+/**
+ * Singleton that manages access to internal data stored as preferences,
+ *
+ * @author jfschmakeit
+ */
+public class SantaPreferences {
+
+ private static final int PREFERENCE_VERSION = 3;
+
+ // for a +/- this value (ms) retrieved offset, the difference is simply
+ // ignored and not adjusted
+ public static final int OFFSET_ACCEPTABLE_RANGE_DIFFERENCE = 120000;
+ private static final String TAG = "SantaPreferences";
+ // Shared time offset
+ private static long TIME_OFFSET = 0L;
+
+ private SharedPreferences settings;
+
+ private static final String PREFENCES_FILENAME = "SantaTracker";
+
+ // Preference parameters
+ private static final String PREF_TIMESTAMP = "PREF_TIMESTAMP";
+ private static final String PREF_NEXT_INFO_ACCESS_TIMESTAMP = "PREF_INFO_API_TIMESTAMP";
+ private static final String PREF_FINGERPRINT = "PREF_FINGERPRINT";
+ private static final String PREF_ROUTEOFFSET = "PREF_ROUTEOFFSET";
+ private static final String PREF_STREAMOFFSET = "PREF_STREAMOFFSET";
+ private static final String PREF_TOTALPRESENTS = "PREF_TOTALPRESENTS";
+
+ private static final String PREF_OFFSET = "PREF_OFFSET";
+ private static final String PREF_SWITCHOFF = "PREF_SWITCHOFF";
+ private static final String PREF_VIDEO1 = "PREF_VIDEO1";
+ private static final String PREF_VIDEO15 = "PREF_VIDEO15";
+ private static final String PREF_VIDEO23 = "PREF_VIDEO23";
+
+ private static final String PREF_CASTDISABLED = "PREF_CASTDISABLED";
+ private static final String PREF_PHOTODISABLED = "PREF_PHOTODISABLED";
+ private static final String PREF_GUMBALLDISABLED = "PREF_GUMBALLDISABLED";
+ private static final String PREF_JETPACKDISABLED = "PREF_JETPACKDISABLED";
+ private static final String PREF_MEMORYDISABLED = "PREF_MEMORYDISABLED";
+ private static final String PREF_ROCKETDISABLED = "PREF_ROCKETDISABLED";
+ private static final String PREF_DANCERDISABLED = "PREF_DANCERDISABLED";
+ private static final String PREF_SNOWDOWNDISABLED = "PREF_SNOWDOWNDISABLED";
+
+ private static final String PREF_RANDVALUE = "PREF_RANDVALUE";
+ private static final String PREF_DBVERSION_DEST = "PREF_DBVERSION";
+ private static final String PREF_DBVERSION_STREAM = "PREF_DBSTREAMVERSION";
+ private static final String PREF_PREFVERSION = "PREF_PREFERENCEVERSION";
+
+ // cached rand value
+ private static float rand;
+
+ public SantaPreferences(Context context) {
+
+ // Get preferences in multi-process mode (required for Info-API task
+ // updates)
+ this.settings = context.getSharedPreferences(PREFENCES_FILENAME,
+ Context.MODE_MULTI_PROCESS);
+
+ // update the cached rand value if it has been set, otherwise it will be
+ // overwritten later
+ rand = settings.getFloat(PREF_RANDVALUE, -1f);
+
+ // initialise time offset
+ SantaPreferences.TIME_OFFSET = getOffset();
+
+ // Handle Preference upgrades
+ checkUpgrade();
+ }
+
+ private void checkUpgrade() {
+ final int storedVersion = settings.getInt(PREF_PREFVERSION, 1);
+
+ if (PREFERENCE_VERSION > storedVersion) {
+ onUpgrade(storedVersion, PREFERENCE_VERSION);
+ }
+ }
+
+ private void onUpgrade(int oldVersion, int preferenceVersion) {
+ Editor editor = settings.edit();
+
+ Log.d(TAG, String.format("Upgrading from %d to %d.", oldVersion, preferenceVersion));
+
+ if (oldVersion >= 1) {
+ // Delete all unused cast flags from v1
+ for (String key : LegacyPrefrences.CastFlags_1.ALL_FLAGS) {
+ editor.remove(key);
+ }
+ // Delete all other unused flags
+ for(String key : LegacyPrefrences.PREFERENCES){
+ editor.remove(key);
+ }
+ }
+
+ // store new preference version
+ editor.putInt(PREF_PREFVERSION, preferenceVersion);
+
+ editor.commit();
+ }
+
+ /**
+ * Returns true if the preferences have been initialised and the database contains valid data.
+ */
+ public boolean hasValidData() {
+ return getFingerprint() != null && getRandValue() >= 0
+ && getDBTimestamp() > 1;
+
+ }
+
+ /**
+ * Returns the hash of the current data stored in the database. Returns {@link Long#MIN_VALUE}
+ * if no hash has been set.
+ */
+ public String getFingerprint() {
+ return settings.getString(PREF_FINGERPRINT, null);
+ }
+
+ public boolean getSwitchOff() {
+ return settings.getBoolean(PREF_SWITCHOFF, false);
+ }
+
+ public void setSwitchOff(boolean switchOff) {
+ Editor editor = settings.edit();
+ editor.putBoolean(PREF_SWITCHOFF, switchOff);
+ editor.commit();
+ }
+
+ /**
+ * Returns the timestamp at which the INFO-API should next be accessed or 0 if not set.
+ */
+ public long getNextInfoAPIAccess() {
+ return settings.getLong(PREF_NEXT_INFO_ACCESS_TIMESTAMP, 0L);
+ }
+
+ public void setNextInfoAPIAccess(long time) {
+ Editor editor = settings.edit();
+ editor.putLong(PREF_NEXT_INFO_ACCESS_TIMESTAMP, time);
+ editor.commit();
+ }
+
+ public int getRouteOffset() {
+ return settings.getInt(PREF_ROUTEOFFSET, 0);
+ }
+
+ public void setRouteOffset(int routeOffset) {
+ Editor editor = settings.edit();
+ editor.putInt(PREF_ROUTEOFFSET, routeOffset);
+ editor.commit();
+ }
+
+ public int getStreamOffset() {
+ return settings.getInt(PREF_STREAMOFFSET, 0);
+ }
+
+ public void setStreamOffset(int streamOffset) {
+ Editor editor = settings.edit();
+ editor.putInt(PREF_STREAMOFFSET, streamOffset);
+ editor.commit();
+ }
+
+ public long getTotalPresents() {
+ return settings.getLong(PREF_TOTALPRESENTS, 0L);
+ }
+
+ public void setTotalPresents(long presents) {
+ Editor editor = settings.edit();
+ editor.putLong(PREF_TOTALPRESENTS, presents);
+ editor.commit();
+ }
+
+ public void invalidateData() {
+ Editor editor = settings.edit();
+ editor.putLong(PREF_TIMESTAMP, Long.MIN_VALUE);
+ editor.putString(PREF_FINGERPRINT, null);
+ editor.putInt(PREF_ROUTEOFFSET, 0);
+ editor.putInt(PREF_STREAMOFFSET, 0);
+ editor.putLong(PREF_TOTALPRESENTS, 0L);
+ editor.commit();
+ }
+
+ public long getOffset() {
+ return settings.getLong(PREF_OFFSET, 0);
+ }
+
+ public void setOffset(long offset) {
+ Editor editor = settings.edit();
+ editor.putLong(PREF_OFFSET, offset);
+ editor.commit();
+ SantaPreferences.TIME_OFFSET = offset;
+ }
+
+ public long getDBTimestamp() {
+ return settings.getLong(PREF_TIMESTAMP, 0);
+ }
+
+ public void setDBTimestamp(long time) {
+ Editor editor = settings.edit();
+ editor.putLong(PREF_TIMESTAMP, time);
+ editor.commit();
+ }
+
+ public float getRandValue() {
+ return rand;
+ }
+
+ public void setRandValue(float value) {
+ Editor editor = settings.edit();
+ editor.putFloat(PREF_RANDVALUE, value);
+ editor.commit();
+
+ rand = value;
+ }
+
+ /**
+ * Stores the fingerprint of the data stored in the database.
+ */
+ public void setFingerprint(String fingerprint) {
+ Editor editor = settings.edit();
+ editor.putString(PREF_FINGERPRINT, fingerprint);
+ editor.commit();
+ }
+
+ public void setGamesDisabled(boolean disableGumball, boolean disableJetpack,
+ boolean disableMemory, boolean disableRocket,
+ boolean disableDancer, boolean disableSnowdown) {
+ Editor editor = settings.edit();
+ editor.putBoolean(PREF_GUMBALLDISABLED, disableGumball);
+ editor.putBoolean(PREF_JETPACKDISABLED, disableJetpack);
+ editor.putBoolean(PREF_MEMORYDISABLED, disableMemory);
+ editor.putBoolean(PREF_ROCKETDISABLED, disableRocket);
+ editor.putBoolean(PREF_DANCERDISABLED, disableDancer);
+ editor.putBoolean(PREF_SNOWDOWNDISABLED, disableSnowdown);
+ editor.commit();
+ }
+
+ public boolean getGumballDisabled() {
+ return settings.getBoolean(PREF_GUMBALLDISABLED, false);
+ }
+
+ public boolean getJetpackDisabled() {
+ return settings.getBoolean(PREF_JETPACKDISABLED, false);
+ }
+
+ public boolean getMemoryDisabled() {
+ return settings.getBoolean(PREF_MEMORYDISABLED, false);
+ }
+
+ public boolean getRocketDisabled() {
+ return settings.getBoolean(PREF_ROCKETDISABLED, false);
+ }
+
+ public boolean getDancerDisabled() {
+ return settings.getBoolean(PREF_DANCERDISABLED, false);
+ }
+
+ public boolean getSnowdownDisabled() {
+ return settings.getBoolean(PREF_SNOWDOWNDISABLED, false);
+ }
+
+ public void setVideos(String video1, String video15, String video23) {
+ Editor editor = settings.edit();
+ editor.putString(PREF_VIDEO1, video1);
+ editor.putString(PREF_VIDEO15, video15);
+ editor.putString(PREF_VIDEO23, video23);
+ editor.commit();
+ }
+
+ public String[] getVideos() {
+ return new String[] {
+ settings.getString(PREF_VIDEO1, null),
+ settings.getString(PREF_VIDEO15, null),
+ settings.getString(PREF_VIDEO23, null)
+ };
+ }
+
+ public void setCastDisabled(boolean disableCast) {
+ Editor editor = settings.edit();
+ editor.putBoolean(PREF_CASTDISABLED, disableCast);
+ editor.commit();
+ }
+
+ public boolean getCastDisabled() {
+ return settings.getBoolean(PREF_CASTDISABLED, false);
+ }
+
+ public void setDestinationPhotoDisabled(boolean disablePhoto) {
+ Editor editor = settings.edit();
+ editor.putBoolean(PREF_PHOTODISABLED, disablePhoto);
+ editor.commit();
+ }
+
+ public boolean getDestinationPhotoDisabled() {
+ return settings.getBoolean(PREF_PHOTODISABLED, false);
+ }
+
+ public void setDestDBVersion(int version) {
+ Editor editor = settings.edit();
+ editor.putInt(PREF_DBVERSION_DEST, version);
+ editor.commit();
+ }
+
+ public int getDestDBVersion() {
+ return settings.getInt(PREF_DBVERSION_DEST, 0);
+ }
+
+ public void setStreamDBVersion(int version) {
+ Editor editor = settings.edit();
+ editor.putInt(PREF_DBVERSION_STREAM, version);
+ editor.commit();
+ }
+
+ public int getStreamDBVersion() {
+ return settings.getInt(PREF_DBVERSION_STREAM, 0);
+ }
+
+ /**
+ * Returns the current time in milliseconds with the offset applied.
+ */
+ public static long getCurrentTime() {
+ return System.currentTimeMillis() + SantaPreferences.TIME_OFFSET;
+ }
+
+ /**
+ * Returns the time adjusted for the offset
+ */
+ public static long getAdjustedTime(long time) {
+ return time - SantaPreferences.TIME_OFFSET;
+ }
+
+ public static int getRandom(int min, int max) {
+ return (int) (min + (Math.random() * ((max - min))));
+ }
+
+ public static double getRandom(double min, double max) {
+ return (min + (Math.random() * ((max - min))));
+ }
+
+ public static float getRandom(float min, float max) {
+ return (min + ((float) Math.random() * ((max - min))));
+ }
+
+ public static void cacheOffset(long offset) {
+ SantaPreferences.TIME_OFFSET = offset;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaStreamContract.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaStreamContract.java
new file mode 100644
index 000000000..8aeede647
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SantaStreamContract.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+public interface SantaStreamContract {
+
+ public static final String TABLE_NAME = "stream";
+
+ // Fields
+ public static final String COLUMN_NAME_ID = "_id"; // SQLite PK
+
+ public static final String COLUMN_NAME_TIMESTAMP = "timestamp";
+
+ public static final String COLUMN_NAME_STATUS = "status";
+ public static final String COLUMN_NAME_DIDYOUKNOW = "didyouknow";
+ public static final String COLUMN_NAME_YOUTUBEID = "youtubeId";
+ public static final String COLUMN_NAME_IMAGEURL = "imageUrl";
+ public static final String COLUMN_NAME_ISNOTIFICATION = "notification";
+
+ // Data types
+ public static final String TEXT_TYPE = " TEXT";
+ public static final String INT_TYPE = " INTEGER";
+ public static final String REAL_TYPE = " REAL";
+ public static final String COMMA_SEP = ",";
+
+ // Boolean values
+ public static final int VALUE_TRUE = 1;
+ public static final int VALUE_FALSE = 0;
+
+ // SQL statements
+ public static final String SQL_CREATE_ENTRIES = "CREATE TABLE "
+ + TABLE_NAME + " (" + COLUMN_NAME_ID + " INTEGER PRIMARY KEY,"
+
+ + COLUMN_NAME_TIMESTAMP + INT_TYPE + " UNIQUE " + COMMA_SEP
+
+ + COLUMN_NAME_STATUS + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_DIDYOUKNOW + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_YOUTUBEID + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_IMAGEURL + TEXT_TYPE + COMMA_SEP
+ + COLUMN_NAME_ISNOTIFICATION + INT_TYPE
+ + " )";
+
+ public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS "
+ + TABLE_NAME;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SqliteCursorLoader.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SqliteCursorLoader.java
new file mode 100644
index 000000000..a57bc6e00
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/SqliteCursorLoader.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.data;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+
+/**
+ * A loader that queries the {@link DestinationDbHelper} and returns a
+ * {@link Cursor}. This class implements the {@link Loader} protocol in a
+ * standard way for querying cursors, building on {@link AsyncTaskLoader} to
+ * perform the cursor query on a background thread so that it does not block the
+ * application's UI.
+ *
+ * The abstract method {@link #getCursor()} needs to be overridden to return the
+ * desired {@link Cursor}.
+ *
+ *
+ * A CursorLoader must be built with the full information for the query to
+ * perform, either through the
+ * {@link #CursorLoader(Context, Uri, String[], String, String[], String)} or
+ * creating an empty instance with {@link #CursorLoader(Context)} and filling in
+ * the desired paramters with {@link #setUri(Uri)},
+ * {@link #setSelection(String)}, {@link #setSelectionArgs(String[])},
+ * {@link #setSortOrder(String)}, and {@link #setProjection(String[])}.
+ *
+ *
+ * Note: This implementation is copied from the ASOP
+ * {@link android.content.CursorLoader} class and modified to directly interact
+ * with the {@link SQLiteDatabase} held by {@link DestinationDbHelper}. See
+ * hackbod's statement at:
+ * https://groups.google.com/d/msg/android-developers/J-Uql3Mn73Y/3haYPQ-pR7sJ
+ */
+public abstract class SqliteCursorLoader extends AsyncTaskLoader {
+
+ final ForceLoadContentObserver mObserver;
+
+ Cursor mCursor;
+
+ /**
+ * Returns the Cursor that is to be loaded.
+ */
+ public abstract Cursor getCursor();
+
+ /* Runs on a worker thread */
+ @Override
+ public Cursor loadInBackground() {
+ // TODO: check if call to new DestinationDBHelper is appropriate here or
+ // whether we can do it in the constructor and keep a handle
+ Cursor cursor = getCursor();
+
+ if (cursor != null) {
+ // Ensure the cursor window is filled
+ cursor.getCount();
+ registerContentObserver(cursor, mObserver);
+ }
+ return cursor;
+ }
+
+ /**
+ * Registers an observer to get notifications from the content provider when
+ * the cursor needs to be refreshed.
+ */
+ void registerContentObserver(Cursor cursor, ContentObserver observer) {
+ cursor.registerContentObserver(mObserver);
+ }
+
+ /* Runs on the UI thread */
+ @Override
+ public void deliverResult(Cursor cursor) {
+ if (isReset()) {
+ // An async query came in while the loader is stopped
+ if (cursor != null) {
+ cursor.close();
+ }
+ return;
+ }
+ Cursor oldCursor = mCursor;
+ mCursor = cursor;
+
+ if (isStarted()) {
+ super.deliverResult(cursor);
+ }
+
+ if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
+ oldCursor.close();
+ }
+ }
+
+ /**
+ * Creates an empty unspecified CursorLoader. You must follow this with
+ * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc to
+ * specify the query to perform.
+ */
+ public SqliteCursorLoader(Context context) {
+ super(context);
+ mObserver = new ForceLoadContentObserver();
+ }
+
+ /**
+ * Starts an asynchronous load of the contacts list data. When the result is
+ * ready the callbacks will be called on the UI thread. If a previous load
+ * has been completed and is still valid the result may be passed to the
+ * callbacks immediately.
+ *
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStartLoading() {
+ if (mCursor != null) {
+ deliverResult(mCursor);
+ }
+ if (takeContentChanged() || mCursor == null) {
+ forceLoad();
+ }
+ }
+
+ /**
+ * Must be called from the UI thread
+ */
+ @Override
+ protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ }
+
+ @Override
+ public void onCanceled(Cursor cursor) {
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ if (mCursor != null && !mCursor.isClosed()) {
+ mCursor.close();
+ }
+ mCursor = null;
+ }
+ /*
+ * @Override public void dump(String prefix, FileDescriptor fd, PrintWriter
+ * writer, String[] args) { super.dump(prefix, fd, writer, args);
+ * writer.print(prefix); writer.print("mUri="); writer.println(mUri);
+ * writer.print(prefix); writer.print("mProjection=");
+ * writer.println(Arrays.toString(mProjection)); writer.print(prefix);
+ * writer.print("mSelection="); writer.println(mSelection);
+ * writer.print(prefix); writer.print("mSelectionArgs=");
+ * writer.println(Arrays.toString(mSelectionArgs)); writer.print(prefix);
+ * writer.print("mSortOrder="); writer.println(mSortOrder);
+ * writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
+ * //invisible field: writer.print(prefix);
+ * writer.print("mContentChanged="); writer.println(mContentChanged); }
+ */
+}
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursor.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursor.java
new file mode 100644
index 000000000..a3517a080
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.database.Cursor;
+
+/**
+ * Encapsulates a cursor of Cards.
+ */
+public class StreamCursor extends CursorHelper implements SantaStreamContract {
+
+ public StreamCursor(Cursor cursor) {
+ super(cursor);
+ }
+
+ /**
+ * Returns true if the Checks whether the departure time of the current
+ * position is in the past.
+ */
+ public boolean isInPast(long time) {
+ return time > mCursor.getLong(mCursor
+ .getColumnIndex(COLUMN_NAME_TIMESTAMP));
+ }
+
+ /**
+ * Returns the {@link StreamEntry} object of the position of the
+ * current
+ * cursor position. If the cursor points at an empty position, a
+ * {@link StreamEntry} object with undefined values is returned.
+ * (The
+ * calling method should verify that the given Cursor is at a valid
+ * position.
+ */
+ @Override
+ protected StreamEntry getParsedObject() {
+ return StreamDbHelper.getCursorEntry(mCursor);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursorLoader.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursorLoader.java
new file mode 100644
index 000000000..4f1d625ef
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamCursorLoader.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.Context;
+import android.database.Cursor;
+
+/**
+ * Loader that returns a Cursor from
+ * {@link com.google.android.apps.santatracker.data.StreamDbHelper#getFollowing(long, boolean)}
+ *
+ * @author jfschmakeit
+ */
+public class StreamCursorLoader extends SqliteCursorLoader {
+
+ private boolean mNotificationOnly = false;
+
+ public StreamCursorLoader(Context context, boolean notificationOnly) {
+ super(context);
+ mNotificationOnly = notificationOnly;
+ }
+
+ @Override
+ public Cursor getCursor() {
+ return StreamDbHelper.getInstance(getContext()).getAllCursor(mNotificationOnly);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamDbHelper.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamDbHelper.java
new file mode 100644
index 000000000..744815f83
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamDbHelper.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class StreamDbHelper extends SQLiteOpenHelper implements
+ SantaStreamContract {
+
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "SantaStream.db";
+
+ private static StreamDbHelper mInstance = null;
+
+ private StreamDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ /**
+ * Access to Singleton object of this class. Creates a new instance if it
+ * has not been created yet.
+ */
+ public static StreamDbHelper getInstance(Context context) {
+ if (mInstance == null) {
+ mInstance = new StreamDbHelper(context.getApplicationContext());
+ }
+ return mInstance;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(SQL_CREATE_ENTRIES);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+
+ public void reinitialise() {
+ SQLiteDatabase db = getWritableDatabase();
+ // delete all entries
+ db.execSQL(SQL_DELETE_ENTRIES);
+
+ onCreate(db);
+
+ db.close();
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onUpgrade(db, oldVersion, newVersion);
+ }
+
+ public void emptyCardTable() {
+ getWritableDatabase().delete(TABLE_NAME, null, null);
+ }
+
+ public int getVersion() {
+ return getReadableDatabase().getVersion();
+ }
+
+ /**
+ * Expects a writeable {@link android.database.sqlite.SQLiteDatabase} - used for batch commits.
+ */
+ public void insert(SQLiteDatabase db, long timestamp, String status, String didYouKnow,
+ String imageUrl, String youtubeId, boolean isNotification) {
+
+ ContentValues cv = new ContentValues();
+
+ cv.put(COLUMN_NAME_TIMESTAMP, timestamp);
+
+ cv.put(COLUMN_NAME_STATUS, status);
+ cv.put(COLUMN_NAME_DIDYOUKNOW, didYouKnow);
+
+ cv.put(COLUMN_NAME_IMAGEURL, imageUrl);
+ cv.put(COLUMN_NAME_YOUTUBEID, youtubeId);
+
+ cv.put(COLUMN_NAME_ISNOTIFICATION, isNotification);
+
+ // TODO: verify whether the db parameter is needed - can we just get
+ // another writeable handle on the db (even if the transaction is
+ // started on a different one?)
+ db.insertOrThrow(TABLE_NAME, null, cv);
+ }
+
+ /**
+ * Return a cursor for all cards. Parameter defines if only wear cards or only non-wear cards
+ * are returned.
+ */
+ public Cursor getAllCursor(boolean notificationOnly) {
+ SQLiteDatabase db = getReadableDatabase();
+ return db.rawQuery(
+ "SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_NAME_ISNOTIFICATION + " = "
+ + getBoolean(notificationOnly) + " ORDER BY " + COLUMN_NAME_TIMESTAMP,
+ null);
+ }
+
+ /**
+ * Returns a cursor for all cards following (and including) the given
+ * timestamp.
+ */
+ public Cursor getFollowing(long time, boolean notificationOnly) {
+ SQLiteDatabase db = getReadableDatabase();
+ return db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_TIMESTAMP + " >= " + Long.toString(time)
+ + " AND " + COLUMN_NAME_ISNOTIFICATION + " = " + getBoolean(notificationOnly)
+ + " ORDER BY " + COLUMN_NAME_TIMESTAMP, null);
+ }
+
+ /**
+ * Returns the card with the given timestamp.
+ */
+ public StreamEntry getTimestamp(long timestamp) {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_TIMESTAMP + " = " + timestamp, null);
+ c.moveToFirst();
+ StreamEntry streamEntry = getCursorEntry(c);
+ c.close();
+
+ return streamEntry;
+ }
+
+
+ /**
+ * Returns the card with the given _id.
+ */
+ public StreamEntry get(int id) {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE "
+ + COLUMN_NAME_ID + " = " + id, null);
+ c.moveToFirst();
+ StreamEntry streamEntry = getCursorEntry(c);
+ c.close();
+
+ return streamEntry;
+ }
+
+ /**
+ * Helper method that converts the cursor to a card object.
+ */
+ public static StreamEntry getCursorEntry(Cursor mCursor) {
+
+ StreamEntry c = new StreamEntry();
+ c.timestamp = mCursor.getLong(mCursor
+ .getColumnIndex(COLUMN_NAME_TIMESTAMP));
+
+ c.santaStatus = mCursor.getString(mCursor
+ .getColumnIndex(COLUMN_NAME_STATUS));
+ c.didYouKnow = mCursor.getString(mCursor
+ .getColumnIndex(COLUMN_NAME_DIDYOUKNOW));
+ c.image = mCursor.getString(mCursor
+ .getColumnIndex(COLUMN_NAME_IMAGEURL));
+ c.video = mCursor.getString(mCursor
+ .getColumnIndex(COLUMN_NAME_YOUTUBEID));
+ c.isNotification = getBoolean(mCursor.getInt(mCursor
+ .getColumnIndex(COLUMN_NAME_ISNOTIFICATION)));
+
+ return c;
+ }
+
+ private static int getBoolean(boolean isTrue) {
+ return isTrue ? VALUE_TRUE : VALUE_FALSE;
+ }
+
+ private static boolean getBoolean(int value) {
+ return value == VALUE_TRUE;
+ }
+
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamEntry.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamEntry.java
new file mode 100644
index 000000000..19bdd8e3a
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/StreamEntry.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+public class StreamEntry {
+
+ public long timestamp;
+ public String santaStatus = null;
+ public String didYouKnow = null;
+ public String image = null;
+ public String video = null;
+ public String caption = null;
+ public boolean isNotification = false;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Switches.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Switches.java
new file mode 100644
index 000000000..aab6e2b7a
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/data/Switches.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.data;
+
+/**
+ * Kill-switches and remotely updatable configuration data.
+ */
+public class Switches {
+ // Kill-switches
+ public boolean disableCastButton;
+ public boolean disableDestinationPhoto;
+ public boolean disableGumballGame;
+ public boolean disableJetpackGame;
+ public boolean disableMemoryGame;
+ public boolean disableRocketGame;
+ public boolean disableDancerGame;
+ public boolean disableSnowdownGame;
+
+ // Video data
+ public String video1;
+ public String video15;
+ public String video23;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/CountdownDreamService.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/CountdownDreamService.java
new file mode 100644
index 000000000..15cf06a86
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/CountdownDreamService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 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.daydream;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.service.dreams.DreamService;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.launch.LaunchCountdown;
+import com.google.android.apps.santatracker.village.VillageView;
+
+import java.util.Random;
+
+/**
+ * Dream service to show Christmas countdown with waving Santa.
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CountdownDreamService extends DreamService
+ implements LaunchCountdown.LaunchCountdownContext {
+
+ private static final String LOG_TAG = "CountdownDreamService";
+
+ private static final long MS = 1000L;
+ private static final int MAX_SANTA_ANI_INTERVAL_IN_SEC = 20;
+
+ final Random mRandom = new Random();
+
+ private LaunchCountdown mCountDown;
+ private DaydreamVillage mVillage;
+ private Animation mWavingAnim;
+ private Handler mWavingHandsHandler;
+
+ private final Runnable mWavingAnimRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // keep waving
+ Log.d(LOG_TAG, "Santa says 'hohooh'");
+ waveSantaHand();
+ mWavingHandsHandler.postDelayed(this, getRandomIntervalTime());
+ }
+ };
+
+ @Override
+ public void onDreamingStarted() {
+ setContentView(R.layout.layout_daydream);
+ initializeDreamView();
+
+ enableCountdown(true);
+ enableSantaAnimation(true);
+ }
+
+ @Override
+ public void onDreamingStopped() {
+ super.onDreamingStopped();
+
+ enableCountdown(false);
+ enableSantaAnimation(false);
+ }
+
+ @Override
+ public void onCountdownFinished() {
+
+ if (mCountDown != null) {
+ mCountDown.cancel();
+ }
+
+ // Change background!
+ ((ImageView)findViewById(R.id.villageBackground))
+ .setImageResource(R.drawable.village_bg_launch);
+
+ mVillage.setPlaneEnabled(false);
+ findViewById(R.id.countdown_container).setVisibility(View.GONE);
+ findViewById(R.id.santa_waving).setVisibility(View.GONE);
+
+ enableSantaAnimation(false);
+ enableCountdown(false);
+ }
+
+ @Override
+ public View getCountdownView() {
+ return findViewById(R.id.countdown_container);
+ }
+
+ @Override
+ public Context getActivityContext() {
+ // DreamService is not an Activity.
+ return null;
+ }
+
+ private void initializeDreamView() {
+ mVillage = new DaydreamVillage(this);
+ mCountDown = new LaunchCountdown(this);
+ mWavingAnim = AnimationUtils.loadAnimation(this, R.anim.santa_wave);
+
+ VillageView villageView = (VillageView) findViewById(R.id.villageView);
+ villageView.setVillage(mVillage);
+
+ View countDownView = findViewById(R.id.countdown_container);
+ countDownView.setVisibility(View.VISIBLE);
+
+ mWavingHandsHandler = new Handler();
+ }
+
+ private void enableCountdown(boolean enable) {
+
+ if (enable) {
+ final long takeoffTime = getResources().getInteger(R.integer.santa_takeoff) * MS;
+ final long currTime = System.currentTimeMillis();
+ mCountDown.startTimer(takeoffTime - currTime);
+ } else {
+ mCountDown.cancel();
+ }
+ }
+
+
+ private void enableSantaAnimation(boolean enable) {
+ // remove any remaining mWavingAnimRunnable instance first.
+ mWavingHandsHandler.removeCallbacks(mWavingAnimRunnable);
+
+ if (enable) {
+ mWavingHandsHandler.post(mWavingAnimRunnable);
+ }
+ }
+
+ private void waveSantaHand() {
+ findViewById(R.id.santa_arm).startAnimation(mWavingAnim);
+ }
+
+ private long getRandomIntervalTime() {
+ return (5 + mRandom.nextInt(MAX_SANTA_ANI_INTERVAL_IN_SEC -5)) * MS;
+ }
+}
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/DaydreamVillage.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/DaydreamVillage.java
new file mode 100644
index 000000000..081fe2421
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/daydream/DaydreamVillage.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 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.daydream;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.view.GestureDetector;
+
+import com.google.android.apps.santatracker.village.HorizontalScrollingImage;
+import com.google.android.apps.santatracker.village.HorizontalScrollingImageGroup;
+import com.google.android.apps.santatracker.village.VillageView;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Village implementation for supporting Daydream on Android TV
+ */
+public class DaydreamVillage implements VillageView.VillageInterface {
+ private HorizontalScrollingImage mImagePlane;
+ private HorizontalScrollingImageGroup mImageClouds;
+ private boolean mPlaneEnabled = true;
+
+ private WeakReference mCtxRef;
+
+ public DaydreamVillage(Context context) {
+ mCtxRef = new WeakReference<>(context);
+ }
+
+
+ public void initialiseVillageViews() {
+
+ final Context ctx = mCtxRef.get();
+
+ if (ctx == null) {
+ return;
+ }
+
+ Resources resources = ctx.getResources();
+
+ int referenceHeight = resources.getInteger(com.google.android.apps.santatracker.village.R.integer.referenceHeight);
+
+ mImagePlane = new HorizontalScrollingImage(com.google.android.apps.santatracker.village.R.drawable.plane, referenceHeight,
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.planeVerticalOffset), true,
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.planePercentagePerSecond));
+
+ mImageClouds = new HorizontalScrollingImageGroup(com.google.android.apps.santatracker.village.R.drawable.cloud,
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.numClouds),
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.skyStart),
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.cloudsEnd),
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.cloudPercentagePerSecond),
+ resources.getInteger(com.google.android.apps.santatracker.village.R.integer.cloudSpeedJitterPercent),
+ referenceHeight);
+
+ mImagePlane.loadImages(resources);
+ mImageClouds.loadImages(resources);
+ }
+
+ public void onDraw(Canvas canvas, int height, int width) {
+
+ if (mPlaneEnabled) {
+ mImagePlane.onDraw(canvas, height, 3 * width, 0);
+ }
+ mImageClouds.onDraw(canvas, height, width, 0);
+ }
+
+ public GestureDetector.OnGestureListener getTouchListener() {
+
+ // Touch is not supported on Daydream
+ return null;
+ }
+
+ public void setPlaneEnabled(boolean planeEnabled) {
+ mPlaneEnabled = planeEnabled;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/GumballActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/GumballActivity.java
new file mode 100644
index 000000000..7c512c214
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/GumballActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.games;
+
+import android.os.Bundle;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.games.gumball.TiltGameFragment;
+import com.google.android.apps.santatracker.launch.StartupActivity;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class GumballActivity extends PlayGamesActivity {
+
+ private static final String TAG = GumballActivity.class
+ .getSimpleName();
+
+ private TiltGameFragment mGumballFragment;
+ private FirebaseAnalytics mMeasurement;
+
+ public GumballActivity() {
+ super(R.layout.activity_gumball, StartupActivity.class);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mGumballFragment = TiltGameFragment.newInstance();
+ this.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.mainFragmentContainer, mGumballFragment).commit();
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_gumball));
+
+ // [ANALYTICS SCREEN]: Gumball
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_gumball);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mGumballFragment != null) {
+ mGumballFragment.onBackKeyPressed();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+ mGumballFragment.onSignInSucceeded();
+ }
+
+ @Override
+ public String getGameId() {
+ return getResources().getString(R.string.gumball_game_id);
+ }
+
+ @Override
+ public String getGameTitle() {
+ return getString(R.string.gumball);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ super.onSignInFailed();
+ mGumballFragment.onSignInFailed();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/MemoryActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/MemoryActivity.java
new file mode 100644
index 000000000..a855c217a
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/MemoryActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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.games;
+
+import android.os.Bundle;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.games.matching.MemoryMatchFragment;
+import com.google.android.apps.santatracker.launch.StartupActivity;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class MemoryActivity extends PlayGamesActivity {
+
+ private MemoryMatchFragment mMemoryMatchFragment;
+ private FirebaseAnalytics mMeasurement;
+
+ public MemoryActivity() {
+ super(R.layout.activity_memory, StartupActivity.class);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mMemoryMatchFragment = MemoryMatchFragment.newInstance();
+ this.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.mainFragmentContainer, mMemoryMatchFragment).commit();
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_memory));
+
+
+ // [ANALYTICS SCREEN]: Memory
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_memory);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mMemoryMatchFragment != null) {
+ mMemoryMatchFragment.onBackKeyPressed();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+ mMemoryMatchFragment.onSignInSucceeded();
+ }
+
+ @Override
+ public String getGameId() {
+ return getResources().getString(R.string.memory_game_id);
+ }
+
+ @Override
+ public String getGameTitle() {
+ return getString(R.string.memory);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ super.onSignInFailed();
+ mMemoryMatchFragment.onSignInFailed();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/TopCropImageView.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/TopCropImageView.java
new file mode 100644
index 000000000..96a530713
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/TopCropImageView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 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.games;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class TopCropImageView extends ImageView {
+
+ public TopCropImageView(Context context) {
+ super(context);
+ }
+
+ public TopCropImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TopCropImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Drawable drawable = getDrawable();
+
+ if (drawable != null) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int diw = drawable.getIntrinsicWidth();
+ if (diw > 0) {
+ int height = width * drawable.getIntrinsicHeight() / diw;
+ setMeasuredDimension(width, height);
+ return;
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/GameActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/GameActivity.java
new file mode 100644
index 000000000..6db5e8899
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/GameActivity.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2015 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.games.common;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.Window;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.PlayGamesFragment;
+import com.google.android.apps.santatracker.games.SignInListener;
+import com.google.android.apps.santatracker.games.gumball.Utils;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.gms.appindexing.AppIndex;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.common.api.Status;
+
+@SuppressLint("Registered")
+public abstract class GameActivity extends AppCompatActivity implements
+ SignInListener {
+
+ private static final String TAG = "GameActivity";
+
+ /** Non-visible fragment encapsulating Play Games logic. **/
+ private PlayGamesFragment mGamesFragment;
+
+ // we need a separate API client for App Indexing
+ // so that view and ViewEnd events can be recorded even
+ // when the user is not signed in
+ GoogleApiClient mApiClient = null;
+ boolean mSignedIn = false;
+
+ // base URL for deep links required by App Indexing
+ // Deep link intent filter entries in AndroidManifest.xml must match
+ // the data part of this prefix
+ private static Uri BASE_APP_URI;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setBackgroundDrawable(new ColorDrawable(
+ ContextCompat.getColor(this, android.R.color.transparent)));
+ }
+
+ mGamesFragment = PlayGamesFragment.getInstance(this, this);
+
+ mApiClient = new GoogleApiClient.Builder(this).addApi(AppIndex.APP_INDEX_API).build();
+ // add App Indexing API
+ BASE_APP_URI = Uri.parse(
+ "android-app://" + getApplicationContext().getPackageName() +
+ "/" + getResources().getString(R.string.santa_tracker_deep_link_prefix));
+
+ if (Utils.hasKitKat()) {
+ ImmersiveModeHelper.setImmersiveSticky(getWindow());
+ ImmersiveModeHelper.installSystemUiVisibilityChangeListener(getWindow());
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (Utils.hasKitKat() && hasFocus) {
+ ImmersiveModeHelper.setImmersiveSticky(getWindow());
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ appIndexingRecordView();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ appIndexingRecordViewEnd();
+ }
+
+ @Override
+ public void onActivityResult(int req, int resp, Intent data) {
+ super.onActivityResult(req, resp, data);
+ mGamesFragment.onActivityResult(req, resp, data);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ mSignedIn = false;
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ mSignedIn = true;
+ }
+
+ public boolean isSignedIn() {
+ return mSignedIn;
+ }
+
+ public void beginUserInitiatedSignIn() {
+ mGamesFragment.beginUserInitiatedSignIn();
+ }
+
+ public GoogleApiClient getGamesApiClient() {
+ return mGamesFragment.getGamesApiClient();
+ }
+
+ /**
+ * Connect the client, record the view using App Indexing API.
+ * We're not connecting the client in the activity lifecycle method
+ * to maintain symmetry with appIndexingRecordViewEnd which
+ * disconnects the client once the view is recorded
+ */
+ private void appIndexingRecordView() {
+ // connect the client
+ mApiClient.connect();
+ // Define a title for your current page, shown in autocompletion UI
+ final String title = getGameTitle();
+ final Uri appUri = getGameDeepLinkUri();
+
+ // Call the App Indexing API view method
+ PendingResult result = AppIndex.AppIndexApi.view(mApiClient, this,
+ appUri, title, null, null);
+
+ result.setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(Status status) {
+ if (status.isSuccess()) {
+ SantaLog.v(TAG, String.format("App Indexing API: Recorded ["
+ + title + "] view successfully."));
+ } else {
+ Log.e(TAG, "App Indexing API: There was an error recording the view."
+ + status.toString());
+ }
+ }
+ });
+ }
+
+ /**
+ * Record the view end using the App Indexing API,
+ * disconnect the client once the view is recorded.
+ */
+ private void appIndexingRecordViewEnd() {
+ final Uri appUri = getGameDeepLinkUri();
+ final String title = getGameTitle();
+ PendingResult result = AppIndex.AppIndexApi.viewEnd(mApiClient, this,
+ appUri);
+ result.setResultCallback(new ResultCallback() {
+ @Override
+ public void onResult(Status status) {
+ mApiClient.disconnect(); // disconnecting here because of a potential race
+ if (status.isSuccess()) {
+ Log.v(TAG, "App Indexing API: Recorded ["
+ + title + "] view end successfully.");
+ } else {
+ Log.e(TAG, "App Indexing API: There was an error recording the view end."
+ + status.toString());
+ }
+ }
+ });
+ }
+
+ /**
+ * See https://developers.google.com/app-indexing/webmasters/appindexingapi
+ * @return deep link representing this game
+ */
+ public Uri getGameDeepLinkUri() {
+ return BASE_APP_URI.buildUpon().appendPath(String.valueOf(getGameId())).build();
+ }
+
+ /**
+ * This is the ID which becomes a part of the deep link.
+ * For example:
+ * android-app://com.google.android.apps.santatracker/http/google.com/santatracker/gumball
+ * This value need not be human-readable, it sent in the Intent back to the
+ * Santa Tracker by the Google App.
+ * When extending this class and providing new deep links, make sure
+ * to update the corresponding intent-filter in AndroidManifest.xml.
+ * @return deep link component to identify the game to app indexing API
+ */
+ public abstract String getGameId();
+
+ /**
+ * This name will be shown to users in the Google app for autocompletion
+ * @return user-visible game title
+ */
+ public abstract String getGameTitle();
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/PlayGamesActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/PlayGamesActivity.java
new file mode 100644
index 000000000..abd73ec08
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/common/PlayGamesActivity.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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.games.common;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.games.Games;
+
+import java.util.HashMap;
+
+public abstract class PlayGamesActivity extends GameActivity {
+
+ // list of achievements we are pending to unlock or increment (waiting for sign in)
+ // Key is the achievement ID, value is the number of steps to increment. 0 means
+ // unlock rather than increment.
+ private HashMap mAchievementsToSend = new HashMap();
+
+ // score we are pending to send (waiting for sign in). Hash of leaderboard ID to score.
+ private HashMap mScoresToSend = new HashMap();
+
+ // id of the layout to load during onCreate
+ private int mLayoutId;
+ protected Class> mBackClass;
+
+ public PlayGamesActivity(int layoutId, Class> backClass) {
+ super();
+ mLayoutId = layoutId;
+ mBackClass = backClass;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(mLayoutId);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ launchStartupActivity();
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ launchStartupActivity();
+ }
+
+ protected void launchStartupActivity() {
+ finish();
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+ tryToSendGameData();
+ }
+
+ // Call from any thread
+ public void postUnlockAchievement(final int achResId) {
+ final String achievementId = getString(achResId);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!mAchievementsToSend.containsKey(achievementId)) {
+ mAchievementsToSend.put(achievementId, 0);
+ }
+ tryToSendGameData();
+ }
+ });
+ }
+
+ // Call from any thread
+ public void postIncrementAchievement(final int achResId, final int steps) {
+ final String achievementId = getString(achResId);
+ if (steps <= 0) {
+ return;
+ }
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!mAchievementsToSend.containsKey(achievementId)) {
+ mAchievementsToSend.put(achievementId, steps);
+ } else {
+ mAchievementsToSend.put(achievementId,
+ mAchievementsToSend.get(achievementId) + steps);
+ }
+ tryToSendGameData();
+ }
+ });
+ }
+
+ // Call from any thread
+ public void postSubmitScore(final int lbResId, final long score) {
+ final String leaderboardId = getString(lbResId);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mScoresToSend.containsKey(leaderboardId)) {
+ long existingScore = mScoresToSend.get(leaderboardId);
+ if (existingScore >= score) {
+ return;
+ }
+ }
+ mScoresToSend.put(leaderboardId, score);
+ tryToSendGameData();
+ }
+ });
+ }
+
+ // Call from UI thread only.
+ private void tryToSendGameData() {
+ if (isSignedIn() && getGamesApiClient().isConnected()) {
+ GoogleApiClient apiClient = getGamesApiClient();
+ for (String achId : mAchievementsToSend.keySet()) {
+ int arg = mAchievementsToSend.get(achId);
+ if (arg <= 0) {
+ Games.Achievements.unlock(apiClient, achId);
+ } else {
+ Games.Achievements.increment(apiClient, achId, arg);
+ }
+ }
+ mAchievementsToSend.clear();
+
+ for (String lbId : mScoresToSend.keySet()) {
+ Games.Leaderboards.submitScore(apiClient, lbId,
+ mScoresToSend.get(lbId));
+ }
+ mScoresToSend.clear();
+ }
+ }
+
+ public void startSignIn() {
+ if (!isSignedIn()) {
+ beginUserInitiatedSignIn();
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/BaseScene.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/BaseScene.java
new file mode 100644
index 000000000..81b2814e2
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/BaseScene.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.games.gumball.Utils;
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+import com.google.android.apps.santatracker.games.simpleengine.Scene;
+import com.google.android.apps.santatracker.games.simpleengine.SceneManager;
+import com.google.android.apps.santatracker.games.simpleengine.SmoothValue;
+import com.google.android.apps.santatracker.games.simpleengine.SoundManager;
+import com.google.android.apps.santatracker.games.simpleengine.game.GameObject;
+import com.google.android.apps.santatracker.games.simpleengine.game.World;
+import com.google.android.apps.santatracker.games.simpleengine.ui.Button;
+import com.google.android.apps.santatracker.games.simpleengine.ui.SimpleUI;
+import com.google.android.apps.santatracker.games.simpleengine.ui.Widget;
+
+import java.util.Random;
+
+public abstract class BaseScene extends Scene implements Widget.WidgetTriggerListener {
+
+ // digit object factory (to display score, etc)
+ protected DigitObjectFactory mDigitFactory;
+ protected GameObjectFactory mObjectFactory;
+
+ protected World mWorld;
+ protected Renderer mRenderer;
+ protected Random mRandom = new Random();
+
+ // score bar object
+ protected GameObject mScoreBarObj;
+
+ // score digit objects
+ protected GameObject[] mScoreDigitObj = new GameObject[GameConfig.ScoreDisplay.DIGIT_COUNT];
+
+ // timer digit objects
+ protected GameObject mClockIconObj = null;
+ protected GameObject[] mTimeDigitObj = new GameObject[GameConfig.TimeDisplay.DIGIT_COUNT];
+
+ // player's current score
+ protected int mScore = 0;
+ protected SmoothValue mDisplayedScore = new SmoothValue(0.0f,
+ GameConfig.ScoreDisplay.UPDATE_SPEED);
+
+ // game ended?
+ protected boolean mGameEnded = false;
+
+ // our UI (buttons, etc)
+ protected SimpleUI mUI = null;
+
+ // widget trigger messages
+ private static final int MSG_REPLAY = 1001;
+ private static final int MSG_SIGN_IN = 1002;
+ private static final int MSG_PAUSE = 1003;
+ private static final int MSG_RESUME = 1004;
+ private static final int MSG_QUIT = 1005;
+ private static final int MSG_SHARE = 1006;
+
+ // sfx IDs
+ protected int mGameOverSfx;
+
+ // paused?
+ protected boolean mPaused = false;
+
+ // back key pressed?
+ private boolean mBackKeyPending = false;
+
+ // DPAD_CENTER key pressed?
+ private boolean mConfirmKeyPending;
+ private long mConfirmKeyEventTime;
+ private final long CENTER_KEY_DELAY_MS = 500;
+
+ // isRunning on Tv?
+ boolean mIsTv;
+
+ // pause and resume buttons
+ Button mPauseButton, mResumeButton;
+
+ // pause curtain, that is, the full screen object we display as a translucent
+ // screen over the whole display when the game is paused
+ GameObject mPauseCurtain = null;
+
+ // the big play button
+ Button mBigPlayButton = null;
+
+ // quit button
+ Button mQuitButton = null;
+
+ // game objects that compose the Sign In ui
+ GameObject mSignInBarObj = null;
+ Button mSignInButton = null;
+ GameObject mSignInTextObj = null;
+
+ // to be implemented by subclasses
+ protected abstract String getBgmAssetFile();
+
+ protected abstract float getDisplayedTime();
+
+ protected abstract BaseScene makeNewScene();
+
+ // are we signed in
+ private boolean mSignedIn = false;
+
+ @Override
+ public void onInstall() {
+
+ // are we signed in?
+ SceneActivity act = (SceneActivity) SceneManager.getInstance().getActivity();
+ if (act != null) {
+ mSignedIn = act.isSignedIn();
+ UiModeManager manger = (UiModeManager)act.getSystemService(Context.UI_MODE_SERVICE);
+ mIsTv = Configuration.UI_MODE_TYPE_TELEVISION == manger.getCurrentModeType();
+ }
+
+ mRenderer = SceneManager.getInstance().getRenderer();
+ mWorld = new World(mRenderer);
+ mDigitFactory = new DigitObjectFactory(mRenderer, mWorld);
+ mDigitFactory.requestTextures(GameConfig.ScoreDisplay.DIGIT_SIZE);
+ mObjectFactory = new GameObjectFactory(mRenderer, mWorld);
+ mObjectFactory.requestTextures();
+
+ mUI = new SimpleUI(mRenderer);
+
+ if (isTv()) {
+ mClockIconObj = mObjectFactory.makeTvClockIcon();
+ mDigitFactory.makeDigitObjects(GameConfig.ScoreDisplay.DIGIT_COUNT, GameConfig.TYPE_DECOR,
+ mRenderer.getRelativePos(GameConfig.ScoreDisplay.POS_X_REL,
+ GameConfig.ScoreDisplay.POS_X_DELTA),
+ mRenderer.getRelativePos(GameConfig.ScoreDisplay.POS_Y_REL_TV,
+ GameConfig.ScoreDisplay.POS_Y_DELTA_TV),
+ GameConfig.ScoreDisplay.DIGIT_SIZE,
+ GameConfig.ScoreDisplay.DIGIT_SPACING, mScoreDigitObj);
+ mBigPlayButton = mObjectFactory.makeBigPlayButton(this, MSG_RESUME);
+ mBigPlayButton.hide();
+ mUI.add(mBigPlayButton);
+
+ float x = GameConfig.TimeDisplay.POS_X_DELTA + GameConfig.TimeDisplay.ICON_SIZE;
+ mDigitFactory.makeDigitObjects(GameConfig.TimeDisplay.DIGIT_COUNT, GameConfig.TYPE_DECOR,
+ mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL, x),
+ mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_Y_REL_TV,
+ GameConfig.TimeDisplay.POS_Y_DELTA_TV),
+ GameConfig.TimeDisplay.DIGIT_SIZE,
+ GameConfig.TimeDisplay.DIGIT_SPACING, mTimeDigitObj);
+
+ mPauseCurtain = mObjectFactory.makePauseCurtain();
+ mPauseCurtain.hide();
+ } else {
+ mClockIconObj = mObjectFactory.makeClockIcon();
+ mScoreBarObj = mObjectFactory.makeScoreBar();
+ mDigitFactory.makeDigitObjects(GameConfig.ScoreDisplay.DIGIT_COUNT, GameConfig.TYPE_DECOR,
+ mRenderer.getRelativePos(GameConfig.ScoreDisplay.POS_X_REL,
+ GameConfig.ScoreDisplay.POS_X_DELTA),
+ mRenderer.getRelativePos(GameConfig.ScoreDisplay.POS_Y_REL,
+ GameConfig.ScoreDisplay.POS_Y_DELTA),
+ GameConfig.ScoreDisplay.DIGIT_SIZE,
+ GameConfig.ScoreDisplay.DIGIT_SPACING, mScoreDigitObj);
+
+ float x = GameConfig.TimeDisplay.POS_X_DELTA + GameConfig.TimeDisplay.ICON_SIZE;
+ mDigitFactory.makeDigitObjects(GameConfig.TimeDisplay.DIGIT_COUNT, GameConfig.TYPE_DECOR,
+ mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL, x),
+ mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_Y_REL,
+ GameConfig.TimeDisplay.POS_Y_DELTA),
+ GameConfig.TimeDisplay.DIGIT_SIZE,
+ GameConfig.TimeDisplay.DIGIT_SPACING, mTimeDigitObj);
+
+ mQuitButton = mObjectFactory.makeQuitButton(this, MSG_QUIT);
+ mQuitButton.hide();
+ mUI.add(mQuitButton);
+
+ mPauseButton = mObjectFactory.makePauseButton(this, MSG_PAUSE);
+ mResumeButton = mObjectFactory.makeResumeButton(this, MSG_RESUME);
+ mResumeButton.hide();
+ mUI.add(mPauseButton);
+ mUI.add(mResumeButton);
+
+ mPauseCurtain = mObjectFactory.makePauseCurtain();
+ mPauseCurtain.hide();
+
+ mBigPlayButton = mObjectFactory.makeBigPlayButton(this, MSG_RESUME);
+ mBigPlayButton.hide();
+ mUI.add(mBigPlayButton);
+ }
+
+ SoundManager soundManager = SceneManager.getInstance().getSoundManager();
+ soundManager.requestBackgroundMusic(getBgmAssetFile());
+ mGameOverSfx = soundManager.requestSfx(R.raw.jetpack_gameover);
+
+ mRenderer.setClearColor(0xffffffff);
+ }
+
+ @Override
+ public void onUninstall() {
+ }
+
+ @Override
+ public void doStandbyFrame(float deltaT) {
+ }
+
+ @Override
+ public void doFrame(float deltaT) {
+ if (mPaused) {
+ deltaT = 0.0f;
+ }
+
+ if (mBackKeyPending) {
+ processBackKey();
+ }
+
+ if (mConfirmKeyPending) {
+ // TODO(chansuk): move a focus based on KeyEvent
+ processCenterKey();
+ }
+
+ // If Activity lost focus and we're playing the game, pause
+ if (!SceneManager.getInstance().shouldBePlaying() && !mGameEnded && !mPaused) {
+ pauseGame();
+ }
+
+ if (!mGameEnded) {
+ updateScore(deltaT);
+ updateTime(deltaT);
+ } else {
+ updateScore(deltaT);
+ checkSignInWidgetsNeeded();
+ }
+
+ mWorld.doFrame(deltaT);
+ }
+
+ private void processBackKey() {
+ mBackKeyPending = false;
+ if (mGameEnded || (mPaused && mIsTv)) {
+ quitGame();
+ } else if (mPaused) {
+ unpauseGame();
+ } else {
+ pauseGame();
+ }
+ }
+
+ private void processCenterKey() {
+ final long currTime = System.currentTimeMillis();
+ if (currTime - mConfirmKeyEventTime < CENTER_KEY_DELAY_MS) {
+ mBigPlayButton.setPressed(true);
+ } else {
+ mConfirmKeyPending = false;
+ mBigPlayButton.setPressed(false);
+ if (mPaused) {
+ unpauseGame();
+ } else if (mGameEnded) {
+ //re-start new game
+ SceneManager.getInstance().requestNewScene(makeNewScene());
+ }
+ }
+ }
+
+ private void updateScore(float deltaT) {
+ if (mGameEnded) {
+ mDisplayedScore.setValue(mScore);
+ } else {
+ mDisplayedScore.setTarget(mScore);
+ mDisplayedScore.update(deltaT);
+ }
+ mDigitFactory.setDigits((int) Math.round(mDisplayedScore.getValue()), mScoreDigitObj);
+ bringObjectsToFront(mScoreDigitObj);
+
+ if (!isTv()) {
+ mScoreBarObj.bringToFront();
+ mPauseButton.bringToFront();
+ mResumeButton.bringToFront();
+ }
+ }
+
+ protected void endGame() {
+ mGameEnded = true;
+ // show the podium object
+ mObjectFactory.makePodium();
+
+ // move score to final position
+ float x = mRenderer.getRelativePos(GameConfig.Podium.ScoreDisplay.X_REL,
+ GameConfig.Podium.ScoreDisplay.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.Podium.ScoreDisplay.Y_REL,
+ GameConfig.Podium.ScoreDisplay.Y_DELTA);
+ displaceObjectsTo(mScoreDigitObj, x, y);
+ bringObjectsToFront(mScoreDigitObj);
+
+ // hide time counter
+ mClockIconObj.hide();
+ hideObjects(mTimeDigitObj);
+
+ // make the "your score is" label
+ mObjectFactory.makeScoreLabel();
+
+ // create the end of game UI and add the "play again" button to it
+ mUI.add(mObjectFactory.makePlayAgainButton(this, MSG_REPLAY));
+
+ if (isTv()) {
+ //TODO(chansuk): tv specific ui layout
+
+ } else {
+ // hide the score bar
+ mScoreBarObj.hide();
+
+ mResumeButton.hide();
+ mPauseButton.hide();
+
+ Button quitButton = mObjectFactory.makePodiumQuitButton(this, MSG_QUIT);
+ mUI.add(quitButton);
+ quitButton.bringToFront();
+ quitButton.show();
+
+ // TODO(samstern): real message
+ Button shareButton = mObjectFactory.makePodiumShareButton(this, MSG_SHARE);
+ mUI.add(shareButton);
+ shareButton.bringToFront();
+ shareButton.show();
+
+ // create the sign in bar and sign in button
+ if (!mSignedIn) {
+ mSignInBarObj = mObjectFactory.makeSignInBar();
+ mSignInTextObj = mObjectFactory.makeSignInText();
+ mUI.add(mSignInButton = mObjectFactory.makeSignInButton(this, MSG_SIGN_IN));
+ }
+ }
+
+ // disable the background music
+ SceneManager.getInstance().getSoundManager().enableBgm(false);
+
+ // play the game over sfx
+ SceneManager.getInstance().getSoundManager().playSfx(mGameOverSfx);
+ }
+
+ protected boolean isTv() {
+ return mIsTv;
+ }
+
+ protected void displaceObjectsTo(GameObject[] objs, float x, float y) {
+ float deltaX = x - objs[0].x;
+ float deltaY = y - objs[0].y;
+ int i;
+ for (i = 0; i < objs.length; i++) {
+ objs[i].displaceBy(deltaX, deltaY);
+ }
+ }
+
+ protected void bringObjectsToFront(GameObject[] objs) {
+ int i;
+ for (i = 0; i < objs.length; i++) {
+ objs[i].bringToFront();
+ }
+ }
+
+ protected void hideObjects(GameObject[] objs) {
+ int i;
+ for (i = 0; i < objs.length; i++) {
+ objs[i].hide();
+ }
+ }
+
+ private void updateTime(float deltaT) {
+ int seconds = (int) Math.ceil(getDisplayedTime());
+ seconds = seconds < 0 ? 0 : seconds > 99 ? 99 : seconds;
+ mDigitFactory.setDigits(seconds, mTimeDigitObj);
+ bringObjectsToFront(mTimeDigitObj);
+ mClockIconObj.bringToFront();
+ }
+
+ @Override
+ public void onScreenResized(int width, int height) {
+
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, float x, float y) {
+ super.onPointerDown(pointerId, x, y);
+ if (mUI != null) {
+ mUI.onPointerDown(pointerId, x, y);
+ }
+ }
+
+ @Override
+ public void onPointerUp(int pointerId, float x, float y) {
+ super.onPointerUp(pointerId, x, y);
+ if (mUI != null) {
+ mUI.onPointerUp(pointerId, x, y);
+ }
+ }
+
+ protected void pauseGame() {
+ if (!mPaused) {
+ mPaused = true;
+ SceneManager.getInstance().getSoundManager().enableBgm(false);
+
+ if (isTv()) {
+ mPauseCurtain.show();
+ mPauseCurtain.bringToFront();
+
+ mBigPlayButton.show();
+ mBigPlayButton.bringToFront();
+ } else {
+ mPauseButton.hide();
+ mResumeButton.show();
+ mPauseCurtain.show();
+ mPauseCurtain.bringToFront();
+
+ mBigPlayButton.show();
+ mBigPlayButton.bringToFront();
+
+ mQuitButton.show();
+ mQuitButton.bringToFront();
+ }
+
+ if (Utils.hasKitKat() && SceneManager.getInstance().getActivity() != null) {
+ SceneManager.getInstance().getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(
+ SceneManager.getInstance().getActivity().getWindow());
+ }
+ });
+ }
+ }
+ }
+
+ protected void unpauseGame() {
+ if (!mPaused) {
+ return;
+ }
+ mPaused = false;
+ SceneManager.getInstance().getSoundManager().enableBgm(true);
+
+ if (isTv()) {
+ mPauseCurtain.hide();
+ mBigPlayButton.hide();
+ } else {
+ mResumeButton.hide();
+ mPauseButton.show();
+ mPauseCurtain.hide();
+ mQuitButton.hide();
+ mBigPlayButton.hide();
+ }
+
+ if (Utils.hasKitKat() && SceneManager.getInstance().getActivity() != null) {
+ SceneManager.getInstance().getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ImmersiveModeHelper.setImmersiveSticky(
+ SceneManager.getInstance().getActivity().getWindow());
+ }
+ });
+ }
+}
+
+ private int roundScore(int score) {
+ score = (score / 50) * 50;
+ return score <= 0 ? 50 : score;
+ }
+
+ @Override
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ if (mUI != null) {
+ mUI.onPointerMove(pointerId, x, y, deltaX, deltaY);
+ }
+ }
+
+ @Override
+ public void onWidgetTriggered(int message) {
+ SceneActivity act;
+
+ switch (message) {
+ case MSG_REPLAY:
+ SceneManager.getInstance().requestNewScene(makeNewScene());
+ break;
+ case MSG_SIGN_IN:
+ act = (SceneActivity) SceneManager.getInstance().getActivity();
+ if (act != null) {
+ // start sign in flow
+ act.beginUserInitiatedSignIn();
+ }
+ break;
+ case MSG_PAUSE:
+ pauseGame();
+ break;
+ case MSG_RESUME:
+ unpauseGame();
+ break;
+ case MSG_QUIT:
+ quitGame();
+ break;
+ case MSG_SHARE:
+ share();
+ break;
+ }
+ }
+
+ private void quitGame() {
+ Activity act = SceneManager.getInstance().getActivity();
+ if (act != null && act instanceof SceneActivity) {
+ ((SceneActivity) act).postQuitGame();
+ }
+ }
+
+ private void share() {
+ Activity act = SceneManager.getInstance().getActivity();
+ if (act != null && act instanceof SceneActivity) {
+ ((SceneActivity) act).share();
+ }
+ }
+
+ private void checkSignInWidgetsNeeded() {
+ if (mSignedIn) {
+ if (mSignInBarObj != null) {
+ mSignInBarObj.hide();
+ }
+ if (mSignInTextObj != null) {
+ mSignInTextObj.hide();
+ }
+ if (mSignInButton != null) {
+ mSignInButton.hide();
+ }
+ }
+ }
+
+ // Caution: Called from the UI thread!
+ public void setSignedIn(boolean signedIn) {
+ mSignedIn = signedIn;
+ }
+
+ // Caution: Called from the UI thread!
+ public boolean onBackKeyPressed() {
+ // raise a flag and process later (on the game thread)
+ mBackKeyPending = true;
+ return true;
+ }
+
+ // Caution: Called from the UI thread!
+ public boolean onConfirmKeyPressed() {
+ // raise a flag and process later (on the game thread)
+ if (mConfirmKeyPending) {
+ return true;
+ }
+
+ mConfirmKeyPending = true;
+ mConfirmKeyEventTime = System.currentTimeMillis();
+ return true;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/DigitObjectFactory.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/DigitObjectFactory.java
new file mode 100644
index 000000000..0b01c6e91
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/DigitObjectFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+import com.google.android.apps.santatracker.games.simpleengine.game.GameObject;
+import com.google.android.apps.santatracker.games.simpleengine.game.World;
+
+public class DigitObjectFactory {
+
+ int mDigitTex[] = new int[10];
+ Renderer mRenderer;
+ World mWorld;
+
+ public DigitObjectFactory(Renderer renderer, World world) {
+ mRenderer = renderer;
+ mWorld = world;
+ }
+
+ public void requestTextures(float maxDigitWidth) {
+ int[] res = new int[]{
+ R.drawable.games_digit_0, R.drawable.games_digit_1, R.drawable.games_digit_2,
+ R.drawable.games_digit_3, R.drawable.games_digit_4, R.drawable.games_digit_5,
+ R.drawable.games_digit_6, R.drawable.games_digit_7, R.drawable.games_digit_8,
+ R.drawable.games_digit_9
+ };
+ for (int i = 0; i < 10; i++) {
+ mDigitTex[i] = mRenderer.requestImageTex(res[i], "digit_" + i,
+ Renderer.DIM_WIDTH, maxDigitWidth);
+ }
+ }
+
+ public GameObject makeDigitObject(int type, float x, float y, float size) {
+ return mWorld.newGameObjectWithImage(type, x, y, mDigitTex[0], size, size);
+ }
+
+ public void setDigit(GameObject digitObject, int digit) {
+ digit = digit > 9 ? 9 : digit < 0 ? 0 : digit;
+ digitObject.getSprite(0).texIndex = mDigitTex[digit];
+ }
+
+ public void makeDigitObjects(int count, int type, float x, float y, float size,
+ float stride, GameObject[] result) {
+ int i;
+ for (i = 0; i < count; i++) {
+ result[i] = makeDigitObject(type, x, y, size);
+ x += stride;
+ }
+ }
+
+ public void setDigits(int valueToShow, GameObject[] digitObjects) {
+ setDigits(valueToShow, digitObjects, 0, digitObjects.length);
+ }
+
+ public void setDigits(int valueToShow, GameObject[] digitObjects, int start, int length) {
+ int i;
+ for (i = start + length - 1; i >= start; --i) {
+ setDigit(digitObjects[i], valueToShow % 10);
+ valueToShow /= 10;
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameConfig.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameConfig.java
new file mode 100644
index 000000000..6ec440cb6
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameConfig.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+
+public class GameConfig {
+
+ // type code for decorative objects (HUD, etc)
+ public static final int TYPE_DECOR = 9999;
+
+ // score popup settings
+ public class ScorePopup {
+
+ public static final float DIGIT_SIZE = 0.04f;
+ public static final float DIGIT_SPACING = 0.022f;
+ public static final float POPUP_VEL_Y = 0.1f;
+ public static final float POPUP_EXPIRE = 0.8f;
+ }
+
+ // score bar settings
+ public class ScoreBar {
+
+ public static final float WIDTH = 0.7f;
+ public static final int X_REL = Renderer.REL_RIGHT;
+ public static final float X_DELTA = -WIDTH / 2;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float Y_DELTA = 0.06f;
+
+ public class PauseButton {
+
+ public static final int X_REL = Renderer.REL_RIGHT;
+ public static final float X_DELTA = -0.1f;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float Y_DELTA = 0.06f;
+ public static final float WIDTH = 0.2f;
+ public static final float HEIGHT = 0.2f;
+ public static final float SPRITE_WIDTH = 0.1f;
+ public static final float SPRITE_HEIGHT = 0.1f;
+ }
+ }
+
+ // score display settings
+ public class ScoreDisplay {
+
+ public static final float DIGIT_SIZE = 0.06f;
+ public static final float DIGIT_SPACING = DIGIT_SIZE * 0.5f;
+ public static final int DIGIT_COUNT = 6;
+ public static final int POS_X_REL = Renderer.REL_RIGHT;
+ public static final float POS_X_DELTA = -0.62f;
+ public static final int POS_Y_REL = Renderer.REL_BOTTOM;
+ public static final float POS_Y_DELTA = 0.062f;
+
+ public static final int POS_Y_REL_TV = Renderer.REL_TOP;;
+ public static final float POS_Y_DELTA_TV = -0.093f;
+
+ public static final float UPDATE_SPEED = 1000.0f;
+ }
+
+ // time display settings
+ public class TimeDisplay {
+
+ public static final float ICON_SIZE = 0.06f;
+ public static final float DIGIT_SIZE = ScoreDisplay.DIGIT_SIZE;
+ public static final float DIGIT_SPACING = ScoreDisplay.DIGIT_SPACING;
+ public static final int DIGIT_COUNT = 2;
+ public static final int POS_X_REL = Renderer.REL_RIGHT;
+ public static final float POS_X_DELTA = -0.33f;
+ public static final int POS_Y_REL = Renderer.REL_BOTTOM;
+ public static final float POS_Y_DELTA = ScoreDisplay.POS_Y_DELTA;
+ public static final int POS_Y_REL_TV = Renderer.REL_TOP;
+ public static final float POS_Y_DELTA_TV = ScoreDisplay.POS_Y_DELTA_TV;
+ }
+
+ // podium (level end) screen settings
+ public class Podium {
+
+ public static final float WIDTH = 0.8f;
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.0f;
+ public static final int Y_REL = Renderer.REL_CENTER;
+ public static final float Y_DELTA = 0.1f;
+
+ // score label (the static text that says "Score")
+ public class ScoreLabel {
+
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.15f;
+ public static final int Y_REL = Renderer.REL_CENTER;
+ public static final float Y_DELTA = 0.2f;
+ public static final float FONT_SIZE = 25.0f;
+ }
+
+ // where do we display the score in the podium screen
+ public class ScoreDisplay {
+
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.07f;
+ public static final int Y_REL = Renderer.REL_CENTER;
+ public static final float Y_DELTA = 0.1f;
+ }
+
+ // "play again" button
+ public class ReplayButton {
+
+ public static final float FONT_SIZE = 25.0f;
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.0f;
+ public static final int Y_REL = Renderer.REL_CENTER;
+ public static final float Y_DELTA = -0.13f;
+ public static final float WIDTH = 0.6f;
+ public static final float HEIGHT = 0.12f;
+ public static final int NORMAL_COLOR = 0xff269e43;
+ public static final int HIGHLIGHT_COLOR = 0xff2db04b;
+ }
+ }
+
+ // Sign in bar
+ public class SignInBar {
+
+ public static final int COLOR = 0x80ffffff;
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.0f;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float HEIGHT = 0.2f;
+ public static final float WIDTH = 10.0f;
+ public static final float Y_DELTA = 0.5f * HEIGHT;
+ }
+
+ // Sign in button
+ public class SignInButton {
+
+ public static final float WIDTH = 0.4f;
+ // (120/402 is the height/width of the image asset)
+ public static final float HEIGHT = WIDTH * (120.0f / 402.0f);
+ public static final int X_REL = Renderer.REL_LEFT;
+ public static final float X_DELTA = WIDTH * 0.5f + 0.05f;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float Y_DELTA = 0.1f;
+
+ public static final float TEXT_DELTA_X = 0.05f;
+
+ public static final float FONT_SIZE = 20.0f;
+ }
+
+ // Sign in encouragement text
+ public class SignInText {
+
+ public static final int COLOR = 0xff25af31;
+ public static final int X_REL = Renderer.REL_LEFT;
+ public static final float X_DELTA = SignInButton.X_DELTA + SignInButton.WIDTH * 0.5f
+ + 0.05f;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float Y_DELTA = 0.1f;
+ public static final int ANCHOR = Renderer.TEXT_ANCHOR_MIDDLE | Renderer.TEXT_ANCHOR_LEFT;
+ public static final float FONT_SIZE = 20.0f;
+ }
+
+ // pause screen settings
+ public class PauseScreen {
+
+ public static final int CURTAIN_COLOR = 0x80ffffff;
+
+ public class BigPlayButton {
+
+ public static final int X_REL = Renderer.REL_CENTER;
+ public static final float X_DELTA = 0.0f;
+ public static final int Y_REL = Renderer.REL_CENTER;
+ public static final float Y_DELTA = 0.0f;
+ public static final float WIDTH = 0.4f;
+ public static final float HEIGHT = 0.4f;
+ public static final float SPRITE_WIDTH = 0.4f;
+ }
+
+ public class QuitBar {
+
+ public static final int X_REL = Renderer.REL_LEFT;
+ public static final float X_DELTA = 0.05f;
+ public static final int Y_REL = Renderer.REL_BOTTOM;
+ public static final float Y_DELTA = 0.058f;
+ public static final float WIDTH = 0.25f;
+ public static final float HEIGHT = WIDTH * 0.5f;
+ public static final float SPRITE_WIDTH = WIDTH;
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameFragment.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameFragment.java
new file mode 100644
index 000000000..116ba7acc
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import com.google.android.apps.santatracker.games.simpleengine.GameView;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class GameFragment extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ GameView view = new GameView(getActivity());
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(false);
+ return view;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameObjectFactory.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameObjectFactory.java
new file mode 100644
index 000000000..80334224b
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/GameObjectFactory.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+import com.google.android.apps.santatracker.games.simpleengine.game.GameObject;
+import com.google.android.apps.santatracker.games.simpleengine.game.World;
+import com.google.android.apps.santatracker.games.simpleengine.ui.Button;
+import com.google.android.apps.santatracker.games.simpleengine.ui.Widget;
+
+import java.util.Arrays;
+import java.util.Random;
+
+public class GameObjectFactory {
+
+ protected Renderer mRenderer;
+ protected World mWorld;
+ protected Random mRandom = new Random();
+
+ // Textures
+ int mTexClock;
+ int mTexPodium;
+ int mPlayAgainTex;
+ int mScoreLabelTex;
+ int mSignInLabelTex;
+ int mSignInNormalTex;
+ int mSignInHighlightTex;
+ int mSignInTextTex;
+ int mScoreBarTex;
+ int mPauseIconTex;
+ int mPauseIconPressedTex;
+ int mResumeIconTex;
+ int mResumeIconPressedTex;
+ int mBigPlayButtonNormalTex;
+ int mBigPlayButtonHighlightTex;
+ int mQuitBarTex;
+ int mQuitBarPressedTex;
+ int mInviteBarTex;
+ int mInviteBarPressedTex;
+
+ public GameObjectFactory(Renderer r, World w) {
+ mRenderer = r;
+ mWorld = w;
+ }
+
+ GameObject[] mTmpDigits = new GameObject[5];
+
+ public void makeScorePopup(float x, float y, int score, DigitObjectFactory df) {
+ int digits = (score >= 10000) ? 5 : (score >= 1000) ? 4 : (score >= 100) ? 3 : 2;
+
+ Arrays.fill(mTmpDigits, null);
+ df.makeDigitObjects(digits, GameConfig.TYPE_DECOR, x, y,
+ GameConfig.ScorePopup.DIGIT_SIZE, GameConfig.ScorePopup.DIGIT_SPACING,
+ mTmpDigits);
+ df.setDigits(score, mTmpDigits, 0, digits);
+
+ int i;
+ for (i = 0; i < digits; i++) {
+ GameObject o = mTmpDigits[i];
+ o.velY = GameConfig.ScorePopup.POPUP_VEL_Y;
+ o.timeToLive = GameConfig.ScorePopup.POPUP_EXPIRE;
+ }
+ }
+
+ protected void requestTextures() {
+ mTexClock = mRenderer.requestImageTex(R.drawable.jetpack_clock, "jetpack_clock",
+ Renderer.DIM_WIDTH, GameConfig.TimeDisplay.ICON_SIZE);
+ mTexPodium = mRenderer.requestImageTex(R.drawable.jetpack_podium, "jetpack_podium",
+ Renderer.DIM_WIDTH, GameConfig.Podium.WIDTH);
+
+ mPlayAgainTex = mRenderer.requestTextTex(R.string.play_again, "play_again",
+ GameConfig.Podium.ReplayButton.FONT_SIZE);
+ mScoreLabelTex = mRenderer.requestTextTex(R.string.score, "score",
+ GameConfig.Podium.ScoreLabel.FONT_SIZE);
+
+ mSignInLabelTex = mRenderer.requestTextTex(R.string.common_signin_button_text,
+ "jetpack_sign_in",
+ GameConfig.SignInButton.FONT_SIZE);
+ mSignInNormalTex = mRenderer.requestImageTex(R.drawable.jetpack_signin, "jetpack_siginin",
+ Renderer.DIM_WIDTH, GameConfig.SignInButton.WIDTH);
+ mSignInHighlightTex = mRenderer.requestImageTex(R.drawable.jetpack_signin_pressed,
+ "jetpack_signin_pressed", Renderer.DIM_WIDTH,
+ GameConfig.SignInButton.WIDTH);
+ mSignInTextTex = mRenderer.requestTextTex(R.string.why_sign_in,
+ "jetpack_why_sign_in", GameConfig.SignInText.FONT_SIZE,
+ GameConfig.SignInText.ANCHOR, GameConfig.SignInText.COLOR);
+ mScoreBarTex = mRenderer.requestImageTex(R.drawable.games_scorebar, "games_scorebar",
+ Renderer.DIM_WIDTH, GameConfig.ScoreBar.WIDTH);
+ mResumeIconTex = mRenderer.requestImageTex(R.drawable.games_play, "games_play",
+ Renderer.DIM_WIDTH, GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH);
+ mResumeIconPressedTex = mRenderer.requestImageTex(R.drawable.games_play_pressed,
+ "games_play_pressed", Renderer.DIM_WIDTH, GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH);
+ mPauseIconTex = mRenderer.requestImageTex(R.drawable.games_pause, "games_pause",
+ Renderer.DIM_WIDTH, GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH);
+ mPauseIconPressedTex = mRenderer.requestImageTex(R.drawable.games_pause_pressed,
+ "games_pause_pressed", Renderer.DIM_WIDTH, GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH);
+ mBigPlayButtonNormalTex = mRenderer.requestImageTex(R.drawable.games_bigplay,
+ "games_bigplay", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH);
+ mBigPlayButtonHighlightTex = mRenderer.requestImageTex(R.drawable.games_bigplay_pressed,
+ "games_bigplay_pressed", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH);
+ mQuitBarTex = mRenderer.requestImageTex(R.drawable.games_cancelbar,
+ "games_cancelbar", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH);
+ mQuitBarPressedTex = mRenderer.requestImageTex(R.drawable.games_cancelbar_pressed,
+ "games_cancelbar_pressed", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH);
+ mInviteBarTex = mRenderer.requestImageTex(R.drawable.games_share,
+ "games_share", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH);
+ mInviteBarPressedTex = mRenderer.requestImageTex(R.drawable.games_share_pressed,
+ "games_share_pressed", Renderer.DIM_WIDTH,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH);
+ }
+
+ public GameObject makeClockIcon() {
+ float x = mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL,
+ GameConfig.TimeDisplay.POS_X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_Y_REL,
+ GameConfig.TimeDisplay.POS_Y_DELTA);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y, mTexClock,
+ GameConfig.TimeDisplay.ICON_SIZE, GameConfig.TimeDisplay.ICON_SIZE);
+ }
+
+ public GameObject makeTvClockIcon() {
+ float x = mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_X_REL,
+ GameConfig.TimeDisplay.POS_X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.TimeDisplay.POS_Y_REL_TV,
+ GameConfig.TimeDisplay.POS_Y_DELTA_TV);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y, mTexClock,
+ GameConfig.TimeDisplay.ICON_SIZE, GameConfig.TimeDisplay.ICON_SIZE);
+ }
+
+ public GameObject makePodium() {
+ float x = mRenderer.getRelativePos(GameConfig.Podium.X_REL, GameConfig.Podium.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.Podium.Y_REL, GameConfig.Podium.Y_DELTA);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y, mTexPodium,
+ GameConfig.Podium.WIDTH, Float.NaN);
+ }
+
+ public GameObject makeScoreBar() {
+ float x = mRenderer.getRelativePos(GameConfig.ScoreBar.X_REL, GameConfig.ScoreBar.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.ScoreBar.Y_REL, GameConfig.ScoreBar.Y_DELTA);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y, mScoreBarTex,
+ GameConfig.ScoreBar.WIDTH, Float.NaN);
+ }
+
+ public GameObject makeScoreLabel() {
+ // create the "score" static label
+ float x = mRenderer.getRelativePos(GameConfig.Podium.ScoreLabel.X_REL,
+ GameConfig.Podium.ScoreLabel.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.Podium.ScoreLabel.Y_REL,
+ GameConfig.Podium.ScoreLabel.Y_DELTA);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y, mScoreLabelTex,
+ Float.NaN, Float.NaN);
+ }
+
+ public Button makePlayAgainButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.Podium.ReplayButton.X_REL,
+ GameConfig.Podium.ReplayButton.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.Podium.ReplayButton.Y_REL,
+ GameConfig.Podium.ReplayButton.Y_DELTA);
+ Button replayButton = new Button(mRenderer, x, y, GameConfig.Podium.ReplayButton.WIDTH,
+ GameConfig.Podium.ReplayButton.HEIGHT);
+ replayButton.addFlatBackground(GameConfig.Podium.ReplayButton.NORMAL_COLOR,
+ GameConfig.Podium.ReplayButton.HIGHLIGHT_COLOR);
+ replayButton.addTex(mPlayAgainTex);
+ replayButton.setClickListener(listener, message);
+ return replayButton;
+ }
+
+ public GameObject makeSignInBar() {
+ float x = mRenderer.getRelativePos(GameConfig.SignInBar.X_REL,
+ GameConfig.SignInBar.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.SignInBar.Y_REL,
+ GameConfig.SignInBar.Y_DELTA);
+ return mWorld
+ .newGameObjectWithColor(GameConfig.TYPE_DECOR, x, y, GameConfig.SignInBar.COLOR,
+ GameConfig.SignInBar.WIDTH, GameConfig.SignInBar.HEIGHT);
+ }
+
+ public GameObject makeSignInText() {
+ float x = mRenderer.getRelativePos(GameConfig.SignInText.X_REL,
+ GameConfig.SignInText.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.SignInText.Y_REL,
+ GameConfig.SignInText.Y_DELTA);
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y,
+ mSignInTextTex, Float.NaN, Float.NaN);
+ }
+
+ public Button makeSignInButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.SignInButton.X_REL,
+ GameConfig.SignInButton.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.SignInButton.Y_REL,
+ GameConfig.SignInButton.Y_DELTA);
+ Button signInButton = new Button(mRenderer, x, y, GameConfig.SignInButton.WIDTH,
+ GameConfig.SignInButton.HEIGHT);
+ signInButton.addNormalTex(mSignInNormalTex);
+ signInButton.addHighlightTex(mSignInHighlightTex);
+ signInButton.addTex(mSignInLabelTex, GameConfig.SignInButton.TEXT_DELTA_X, 0.0f,
+ Float.NaN, Float.NaN);
+ signInButton.setClickListener(listener, message);
+ return signInButton;
+ }
+
+ private Button makePauseOrResumeButton(boolean isPause, Widget.WidgetTriggerListener listener,
+ int message) {
+ float x = mRenderer.getRelativePos(GameConfig.ScoreBar.PauseButton.X_REL,
+ GameConfig.ScoreBar.PauseButton.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.ScoreBar.PauseButton.Y_REL,
+ GameConfig.ScoreBar.PauseButton.Y_DELTA);
+ Button button = new Button(mRenderer, x, y, GameConfig.ScoreBar.PauseButton.WIDTH,
+ GameConfig.ScoreBar.PauseButton.HEIGHT);
+ button.addNormalTex(isPause ? mPauseIconTex : mResumeIconTex, 0.0f, 0.0f,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_HEIGHT);
+ button.addHighlightTex(isPause ? mPauseIconPressedTex : mResumeIconPressedTex, 0.0f, 0.0f,
+ GameConfig.ScoreBar.PauseButton.SPRITE_WIDTH,
+ GameConfig.ScoreBar.PauseButton.SPRITE_HEIGHT);
+ button.setClickListener(listener, message);
+ return button;
+ }
+
+ public Button makePauseButton(Widget.WidgetTriggerListener listener, int message) {
+ return makePauseOrResumeButton(true, listener, message);
+ }
+
+ public Button makeResumeButton(Widget.WidgetTriggerListener listener, int message) {
+ return makePauseOrResumeButton(false, listener, message);
+ }
+
+ public GameObject makePauseCurtain() {
+ GameObject o = mWorld.newGameObject(GameConfig.TYPE_DECOR, 0.0f, 0.0f);
+ Renderer.Sprite sp = o.getSprite(o.addSprite());
+ sp.width = mRenderer.getWidth() + 0.1f; // safety margin
+ sp.height = mRenderer.getHeight() + 0.1f; // safety margin
+ sp.color = GameConfig.PauseScreen.CURTAIN_COLOR;
+ sp.tintFactor = 0.0f;
+ return o;
+ }
+
+ public Button makeBigPlayButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.PauseScreen.BigPlayButton.X_REL,
+ GameConfig.PauseScreen.BigPlayButton.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.PauseScreen.BigPlayButton.Y_REL,
+ GameConfig.PauseScreen.BigPlayButton.Y_DELTA);
+ Button button = new Button(mRenderer, x, y, GameConfig.PauseScreen.BigPlayButton.WIDTH,
+ GameConfig.PauseScreen.BigPlayButton.HEIGHT);
+ button.addNormalTex(mBigPlayButtonNormalTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH, Float.NaN);
+ button.addHighlightTex(mBigPlayButtonHighlightTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.BigPlayButton.SPRITE_WIDTH, Float.NaN);
+ button.setClickListener(listener, message);
+ return button;
+ }
+
+ public Button makeQuitButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.X_REL,
+ GameConfig.PauseScreen.QuitBar.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.Y_REL,
+ GameConfig.PauseScreen.QuitBar.Y_DELTA);
+ Button button = new Button(mRenderer, x, y, GameConfig.PauseScreen.QuitBar.WIDTH,
+ GameConfig.PauseScreen.QuitBar.HEIGHT);
+ button.addNormalTex(mQuitBarTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.addHighlightTex(mQuitBarPressedTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.setClickListener(listener, message);
+ return button;
+ }
+
+ // As above, but anchored at the top of screen (hence -y)
+ public Button makePodiumQuitButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.X_REL,
+ GameConfig.PauseScreen.QuitBar.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.Y_REL,
+ GameConfig.PauseScreen.QuitBar.Y_DELTA);
+ Button button = new Button(mRenderer, x, -y, GameConfig.PauseScreen.QuitBar.WIDTH,
+ GameConfig.PauseScreen.QuitBar.HEIGHT);
+ button.addNormalTex(mQuitBarTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.addHighlightTex(mQuitBarPressedTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.setClickListener(listener, message);
+ return button;
+ }
+
+ // As above, but anchored at the top of screen (hence -y)
+ public Button makePodiumShareButton(Widget.WidgetTriggerListener listener, int message) {
+ float x = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.X_REL,
+ GameConfig.PauseScreen.QuitBar.X_DELTA);
+ float y = mRenderer.getRelativePos(GameConfig.PauseScreen.QuitBar.Y_REL,
+ GameConfig.PauseScreen.QuitBar.Y_DELTA);
+ Button button = new Button(mRenderer, -x, -y, GameConfig.PauseScreen.QuitBar.WIDTH,
+ GameConfig.PauseScreen.QuitBar.HEIGHT);
+ button.addNormalTex(mInviteBarTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.addHighlightTex(mInviteBarPressedTex, 0.0f, 0.0f,
+ GameConfig.PauseScreen.QuitBar.SPRITE_WIDTH, Float.NaN);
+ button.setClickListener(listener, message);
+ return button;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/SceneActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/SceneActivity.java
new file mode 100644
index 000000000..1f8b5b845
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gamebase/SceneActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.games.gamebase;
+
+import android.os.Bundle;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.games.simpleengine.Scene;
+import com.google.android.apps.santatracker.games.simpleengine.SceneManager;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+
+public abstract class SceneActivity extends PlayGamesActivity {
+
+ private AppInvitesFragment mInvitesFragment;
+
+ protected abstract Scene getGameScene();
+
+ public SceneActivity(int layoutId, Class> backClass) {
+ super(layoutId, backClass);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ SceneManager.getInstance().enableDebugLog(getResources().getBoolean(
+ R.bool.debug_logs_enabled));
+ SceneManager.getInstance().requestNewScene(getGameScene());
+ }
+
+ mInvitesFragment = AppInvitesFragment.getInstance(this);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ SceneManager.getInstance().onFocusChanged(hasFocus);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ SceneManager.getInstance().onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ SceneManager.getInstance().onResume(this);
+ }
+
+ @Override
+ public void onSignInFailed() {
+ super.onSignInFailed();
+
+ // communicate to the BaseScene that we are no longer signed in
+ Scene s = SceneManager.getInstance().getCurrentScene();
+ if (s instanceof BaseScene) {
+ ((BaseScene) s).setSignedIn(false);
+ }
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ super.onSignInSucceeded();
+
+ // communicate to the BaseScene that we are no longer signed in
+ Scene s = SceneManager.getInstance().getCurrentScene();
+ if (s instanceof BaseScene) {
+ ((BaseScene) s).setSignedIn(true);
+ }
+ }
+
+ public void postQuitGame() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ launchStartupActivity();
+ }
+ });
+ }
+
+ public void share() {
+ mInvitesFragment.sendGenericInvite();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java
new file mode 100644
index 000000000..fe7a5d240
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Edges.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+import org.jbox2d.collision.shapes.EdgeShape;
+import org.jbox2d.common.Vec2;
+
+/**
+ * Static methods to get the edge paths of the scene
+ *
+ * @author kchapman
+ */
+public class Edges {
+
+ public static EdgeShape[] getCaneEnd() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ //rounded part
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.22f, .858f), new Vec2(0.22f, 1.02f));
+ //bottom
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.04f, .84f), new Vec2(.2f, .84f));
+ //top
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.03f, 1.04f), new Vec2(.2f, 1.04f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneEndFlip() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ //rounded part
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.04f, .858f), new Vec2(0.04f, 1.02f));
+ //bottom
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.06f, .845f), new Vec2(.263f, .845f));
+ //top
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.06f, 1.04f), new Vec2(.263f, 1.04f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneEndReverse() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ //rounded part
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.22f, .128f), new Vec2(0.22f, .29f));
+ //bottom
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.04f, .11f), new Vec2(.2f, .11f));
+ //top
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.03f, .31f), new Vec2(.2f, .31f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneEndReverseFlip() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ //rounded part
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.04f, .128f), new Vec2(0.04f, .29f));
+ //connector
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.06f, .31f), new Vec2(0.04f, .29f));
+ //top
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.06f, .31f), new Vec2(.263f, .31f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainLongShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .845f), new Vec2(5.53f, .845f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .005f), new Vec2(5.53f, .205f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, 1.045f), new Vec2(5.53f, 1.045f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .005f), new Vec2(0f, .205f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainReverseLongShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .115f), new Vec2(5.53f, .115f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .735f), new Vec2(5.53f, .935f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, .315f), new Vec2(5.53f, .315f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .735f), new Vec2(0f, .935f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainMedShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .845f), new Vec2(4.13f, .845f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .005f), new Vec2(5.53f, .205f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, 1.045f), new Vec2(4.13f, 1.045f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .005f), new Vec2(0f, .205f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainReverseMedShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .115f), new Vec2(4.13f, .115f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .735f), new Vec2(5.53f, .935f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, .315f), new Vec2(4.13f, .315f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .735f), new Vec2(0f, .935f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainSmallShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .845f), new Vec2(2.73f, .845f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .005f), new Vec2(5.53f, .205f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, 1.045f), new Vec2(2.73f, 1.045f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .005f), new Vec2(0f, .205f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainReverseSmallShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .115f), new Vec2(2.73f, .115f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .735f), new Vec2(5.53f, .935f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, .315f), new Vec2(2.73f, .315f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .735f), new Vec2(0f, .935f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainTinyShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .845f), new Vec2(1.38f, .845f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .005f), new Vec2(5.53f, .205f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, 1.045f), new Vec2(1.38f, 1.045f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .005f), new Vec2(0f, .205f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainReverseTinyShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .115f), new Vec2(1.38f, .115f));
+ // edgeShapes[1] = new EdgeShape();
+ // edgeShapes[1].set(new Vec2(5.53f, .735f), new Vec2(5.53f, .935f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, .315f), new Vec2(1.38f, .315f));
+ // edgeShapes[3] = new EdgeShape();
+ // edgeShapes[3].set(new Vec2(0f, .735f), new Vec2(0f, .935f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneHookShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[8];
+ // inner top
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.1f, .82f), new Vec2(.24f, .97f));
+ // inner bottom
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(.53f, 1.04f), new Vec2(.77f, 1.04f));
+ // inner edge
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(.24f, .185f), new Vec2(.24f, .97f));
+ // end part
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(.6f, .138f), new Vec2(.6f, .285f));
+
+ // back edge
+ edgeShapes[4] = new EdgeShape();
+ edgeShapes[4].set(new Vec2(.1f, .33f), new Vec2(.1f, .82f));
+ edgeShapes[5] = new EdgeShape();
+ edgeShapes[5].set(new Vec2(.40f, .83f), new Vec2(.40f, 1.03f));
+ edgeShapes[6] = new EdgeShape();
+ edgeShapes[6].set(new Vec2(.53f, 1.04f), new Vec2(.40f, 1.03f));
+ edgeShapes[7] = new EdgeShape();
+ edgeShapes[7].set(new Vec2(.24f, .97f), new Vec2(.40f, 1.03f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneHookFlipShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[8];
+ // inner top
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.30f, 1.04f), new Vec2(.40f, 1.03f));
+ // inner bottom
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0f, 1.04f), new Vec2(.30f, 1.04f));
+ // inner edge
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(.53f, .185f), new Vec2(.527f, .97f));
+ // end part
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(.155f, .138f), new Vec2(.155f, .285f));
+
+ // back edge
+ edgeShapes[4] = new EdgeShape();
+ edgeShapes[4].set(new Vec2(.68f, .33f), new Vec2(.68f, .82f));
+ edgeShapes[5] = new EdgeShape();
+ edgeShapes[5].set(new Vec2(.40f, .83f), new Vec2(.40f, 1.03f));
+
+ edgeShapes[6] = new EdgeShape();
+ edgeShapes[6].set(new Vec2(.40f, 1.03f), new Vec2(.527f, .97f));
+
+ edgeShapes[7] = new EdgeShape();
+ edgeShapes[7].set(new Vec2(.527f, .97f), new Vec2(.68f, .82f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneHookReverseShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[7];
+ // inner top
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.24f, .315f), new Vec2(.77f, .315f));
+
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(.24f, .97f), new Vec2(.40f, 1.03f));
+ // inner edge
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(.24f, .185f), new Vec2(.24f, .97f));
+ // end part
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(.6f, .858f), new Vec2(.6f, 1.005f));
+ // top part
+ edgeShapes[4] = new EdgeShape();
+ edgeShapes[4].set(new Vec2(.6f, 1.005f), new Vec2(.40f, 1.03f));
+ // back edge
+ edgeShapes[5] = new EdgeShape();
+ edgeShapes[5].set(new Vec2(.1f, .33f), new Vec2(.1f, .82f));
+ edgeShapes[6] = new EdgeShape();
+ edgeShapes[6].set(new Vec2(.1f, .82f), new Vec2(.24f, .97f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneHookReverseFlipShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[6];
+ // inner top
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0f, .315f), new Vec2(.53f, .315f));
+
+ // inner edge
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(.53f, .185f), new Vec2(.53f, .97f));
+ // end part
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(.155f, .858f), new Vec2(.155f, .99f));
+ // top part
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(.155f, .99f), new Vec2(.3155f, 1.045f));
+ // back edge
+ edgeShapes[4] = new EdgeShape();
+ edgeShapes[4].set(new Vec2(.68f, .33f), new Vec2(.68f, .82f));
+ edgeShapes[5] = new EdgeShape();
+ edgeShapes[5].set(new Vec2(.53f, .97f), new Vec2(.3155f, 1.045f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getPipeSideEdges() {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.83f, -1f), new Vec2(.01f, .45f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(2.4f, -1f), new Vec2(3.2f, .45f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainSmallAngleNineShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[4];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.01f, 0.935f), new Vec2(3.66f, 0.325f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.25f, 0.675f), new Vec2(3.6f, 0.105f));
+ //backstop
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.15f, 0.755f), new Vec2(0.28f, 1.55f));
+ //end
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(3.66f, 0.128f), new Vec2(3.70f, 0.295f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainSmallAngleTwelveShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[4];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.01f, 0.73f), new Vec2(2.04f, 0.305f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.25f, 0.475f), new Vec2(2.0f, 0.1f));
+ //backstop
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.18f, 0.725f), new Vec2(0.29f, 1.30f));
+ //end
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(2.01f, 0.128f), new Vec2(2.05f, 0.293f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainTinyAngleSixShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[7];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.10f, 0.33f), new Vec2(1.9f, 0.425f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.15f, 0.105f), new Vec2(1.8f, 0.20f));
+ //backstop
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(1.94f, 0.425f), new Vec2(1.91f, 1.07f));
+ //end
+ edgeShapes[3] = new EdgeShape();
+ edgeShapes[3].set(new Vec2(0.07f, 0.128f), new Vec2(0.06f, 0.313f));
+
+ edgeShapes[4] = new EdgeShape();
+ edgeShapes[4].set(new Vec2(1.55f, .96f), new Vec2(1.55f, 1.09f));
+ edgeShapes[5] = new EdgeShape();
+ edgeShapes[5].set(new Vec2(1.55f, 1.09f), new Vec2(1.66f, 1.13f));
+ edgeShapes[6] = new EdgeShape();
+ edgeShapes[6].set(new Vec2(1.91f, 1.07f), new Vec2(1.66f, 1.13f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainSmallAngleSixShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.05f, 0.515f), new Vec2(2.69f, 0.329f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.30f, 0.285f), new Vec2(2.66f, 0.119f));
+ //backstop
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(0.15f, 0.455f), new Vec2(0.27f, 1.15f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainMedAngleSixShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.06f, 0.53f), new Vec2(2.97f, 1.05f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.1f, 0.329f), new Vec2(3.26f, 0.90f));
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(2.97f, 1.05f), new Vec2(3.26f, 0.90f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getCaneMainLargeAngleSixShapes() {
+ EdgeShape[] edgeShapes = new EdgeShape[3];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(0.06f, 0.24f), new Vec2(4.88f, 1.08f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(0.12f, 0.009f), new Vec2(5.19f, 0.95f));
+ edgeShapes[2] = new EdgeShape();
+ edgeShapes[2].set(new Vec2(4.88f, 1.08f), new Vec2(5.22f, 0.95f));
+ return edgeShapes;
+ }
+
+ public static EdgeShape[] getEdges(int edgeType) {
+ switch (edgeType) {
+ case TiltGameView.CANE_MAIN_TINY:
+ return getCaneMainTinyShapes();
+ case TiltGameView.CANE_MAIN_MEDIUM:
+ return getCaneMainMedShapes();
+ case TiltGameView.CANE_MAIN_LONG:
+ return getCaneMainLongShapes();
+ case TiltGameView.CANE_MAIN_SMALL:
+ return getCaneMainSmallShapes();
+ case TiltGameView.CANE_MAIN_TINY_REVERSE:
+ return getCaneMainReverseTinyShapes();
+ case TiltGameView.CANE_MAIN_MEDIUM_REVERSE:
+ return getCaneMainReverseMedShapes();
+ case TiltGameView.CANE_MAIN_LONG_REVERSE:
+ return getCaneMainReverseLongShapes();
+ case TiltGameView.CANE_MAIN_SMALL_REVERSE:
+ return getCaneMainReverseSmallShapes();
+ case TiltGameView.CANE_HOOK:
+ return getCaneHookShapes();
+ case TiltGameView.CANE_HOOK_REVERSE:
+ return getCaneHookReverseShapes();
+ case TiltGameView.CANE_HOOK_FLIP:
+ return getCaneHookFlipShapes();
+ case TiltGameView.CANE_HOOK_REVERSE_FLIP:
+ return getCaneHookReverseFlipShapes();
+ case TiltGameView.CANE_END:
+ return getCaneEnd();
+ case TiltGameView.CANE_END_REVERSE:
+ return getCaneEndReverse();
+ case TiltGameView.CANE_END_FLIP:
+ return getCaneEndFlip();
+ case TiltGameView.CANE_END_REVERSE_FLIP:
+ return getCaneEndReverseFlip();
+
+ case TiltGameView.CANE_MAIN_SMALL_ANGLE_NINE:
+ return getCaneMainSmallAngleNineShapes();
+ case TiltGameView.CANE_MAIN_SMALL_ANGLE_SIX:
+ return getCaneMainSmallAngleSixShapes();
+ case TiltGameView.CANE_MAIN_SMALL_ANGLE_TWELVE:
+ return getCaneMainSmallAngleTwelveShapes();
+ case TiltGameView.CANE_MAIN_REVERSE_TINY_ANGLE_SIX:
+ return getCaneMainTinyAngleSixShapes();
+
+ case TiltGameView.CANE_MAIN_LARGE_ANGLE_SIX:
+ return getCaneMainLargeAngleSixShapes();
+ case TiltGameView.CANE_MAIN_MED_ANGLE_SIX:
+ return getCaneMainMedAngleSixShapes();
+ }
+ return null;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java
new file mode 100644
index 000000000..3deeb7af8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Gumball.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+import java.util.UUID;
+
+/**
+ * @author kchapman
+ */
+public class Gumball {
+
+ public float mXInitPos;
+ public float mYInitPos;
+ public UUID mSoundPoolId;
+ public int mGumballColorId;
+
+ public Gumball() {
+
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/PhysicsWorld.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/PhysicsWorld.java
new file mode 100644
index 000000000..118fd8b9f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/PhysicsWorld.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+import org.jbox2d.collision.shapes.CircleShape;
+import org.jbox2d.collision.shapes.EdgeShape;
+import org.jbox2d.collision.shapes.PolygonShape;
+import org.jbox2d.collision.shapes.Shape;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyDef;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.FixtureDef;
+import org.jbox2d.dynamics.World;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wraps the game world and physics simulation for the gumball game.
+ */
+public class PhysicsWorld {
+
+ /**
+ * All {@link org.jbox2d.dynamics.Body} objects in the world.
+ */
+ private List mBodies = new ArrayList();
+ /**
+ * The Physics world.
+ */
+ private World mWorld;
+ /**
+ * Bodies that are to be removed from the scene.
+ */
+ public List mBodiesToBeRemoved = new ArrayList();
+ /**
+ * Render refresh rate.
+ */
+ private static final float FRAME_RATE = 1.0f / 45.0f;
+ /**
+ * Create the physics world and draws the boundries
+ */
+ public void create(Vec2 gravity) {
+
+ // Create Physics World with Gravity
+ mWorld = new World(gravity);
+ mWorld.setAllowSleep(false);
+ mWorld.setSleepingAllowed(false);
+ mWorld.setAutoClearForces(true);
+
+ BodyDef groundBodyDef = new BodyDef();
+
+ // Create Ground Box
+ groundBodyDef.position.set(new Vec2(5.0f, -2.0f));
+ Body groundBody = mWorld.createBody(groundBodyDef);
+ PolygonShape polygonShape = new PolygonShape();
+
+ // Create top bound
+ groundBodyDef.position.set(new Vec2(5.0f, 32.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+
+ polygonShape.setAsBox(2.0f, 18.0f);
+
+ // Create left wall
+ groundBodyDef.position.set(new Vec2(-2.0f, 16.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+
+ // Create right wall
+ groundBodyDef.position.set(new Vec2(12.0f, 16.0f));
+ groundBody = mWorld.createBody(groundBodyDef);
+ groundBody.createFixture(polygonShape, 1.0f);
+
+ }
+
+ /**
+ * Adds a gumball to the scene.
+ */
+ public void addGumball(float x, float y, Gumball gumball, float density, float radius,
+ float bounce, float friction, BodyType bodyType) {
+ // Create Shape with Properties
+ CircleShape circleShape = new CircleShape();
+ circleShape.m_radius = radius;
+ addItem(x, y, circleShape, bounce, gumball, density, friction, bodyType);
+ }
+
+ public void addPipeSides(float x, float y, int data, float density, float bounce,
+ float friction, BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[2];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.23f, -1f), new Vec2(.01f, .48f));
+ edgeShapes[1] = new EdgeShape();
+ edgeShapes[1].set(new Vec2(1.4f, -1f), new Vec2(1.55f, .45f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+
+ public void addPipeBottom(float x, float y, int data, float density, float bounce,
+ float friction, BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[1];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(.83f, 0f), new Vec2(2.40f, 0f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+ public void addFloor(float x, float y, int data, float density, float bounce, float friction,
+ BodyType bodyType) {
+ EdgeShape[] edgeShapes = new EdgeShape[1];
+ edgeShapes[0] = new EdgeShape();
+ edgeShapes[0].set(new Vec2(-9f, -.8f), new Vec2(9f, -.8f));
+ addItem(x, y, edgeShapes, bounce, data, density, friction, bodyType);
+ }
+
+ public void addItem(float x, float y, Shape[] shapes, float bounce, int data, float density,
+ float friction, BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = data;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ for (int i = 0; i < shapes.length; i++) {
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shapes[i];
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+
+ body.createFixture(fixtureDef);
+ }
+ }
+
+ public void addItem(float x, float y, Shape shape, float bounce, int data, float density,
+ float friction, BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = data;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shape;
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+ body.createFixture(fixtureDef);
+ }
+
+ public void addItem(float x, float y, Shape shape, float bounce, Gumball gumball,
+ float density, float friction, BodyType bodyType) {
+
+ // Create Dynamic Body
+ BodyDef bodyDef = new BodyDef();
+ bodyDef.position.set(x, y);
+ bodyDef.userData = gumball;
+ bodyDef.type = bodyType;
+ Body body = mWorld.createBody(bodyDef);
+ mBodies.add(body);
+
+ // Assign shape to Body
+ FixtureDef fixtureDef = new FixtureDef();
+ fixtureDef.shape = shape;
+ fixtureDef.density = density;
+ fixtureDef.friction = friction;
+ fixtureDef.restitution = bounce;
+ body.createFixture(fixtureDef);
+ }
+
+ /**
+ * Updates the physics world by removing all pending bodies.
+ */
+ public void update() {
+ // Update Physics World
+ for (int i = 0; i < mBodiesToBeRemoved.size(); i++) {
+ mWorld.destroyBody(mBodiesToBeRemoved.get(i));
+ }
+ mBodiesToBeRemoved.clear();
+ mWorld.step(FRAME_RATE, 10, 10);
+ mWorld.clearForces();
+ }
+
+ /**
+ * Gets a reference to the world.
+ */
+ public World getWorld() {
+ return mWorld;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameFragment.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameFragment.java
new file mode 100644
index 000000000..f9fd63344
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameFragment.java
@@ -0,0 +1,1230 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.games.matching.CircleView;
+import com.google.android.apps.santatracker.games.matching.LevelTextView;
+import com.google.android.apps.santatracker.games.matching.MatchingGameConstants;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+
+import org.jbox2d.callbacks.ContactImpulse;
+import org.jbox2d.callbacks.ContactListener;
+import org.jbox2d.collision.Manifold;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.contacts.Contact;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Gumball game fragment.
+ */
+public class TiltGameFragment extends Fragment
+ implements SensorEventListener, ContactListener, AnimationListener, OnClickListener {
+
+ /**
+ * Bounce rate of objects in the physics world.
+ */
+ public static final float WORLD_OBJECT_BOUNCE = 0.2f;
+ /**
+ * Density of objects in the physics world.
+ */
+ public static final float WORLD_OBJECT_DENSITY = 185.77f;
+ /**
+ * Friction of objects in the physics world.
+ */
+ public static final float WORLD_OBJECT_FRICTION = 0.2f;
+ /**
+ * Friction of floor objects in the physics world.
+ */
+ public static final float WORLD_FLOOR_FRICTION = 0.8f;
+ /**
+ * Initial X position of the floor and pipes in the physics world.
+ */
+ public static final float WORLD_FLOOR_X = 3.37f;
+ /*
+ * Initial Y position of the floor and pipes in the physics world.
+ */
+ public static final float WORLD_FLOOR_Y = 0f;
+
+ /** View that contains the main game. */
+ private TiltGameView mGameView;
+
+ /**
+ * Box2D physics world for this game.
+ */
+ private PhysicsWorld mWorld;
+
+ /**
+ * Current rotation of the device. Used to adjust sensor readings if the screen is rotate in
+ * portrait or landscape.
+ *
+ * @see android.view.Display#getRotation()
+ */
+ private int mRotation;
+
+ /**
+ * Main game thread.
+ */
+ private Runnable mGameThread;
+
+ /**
+ * Previous value of the sensor's Y reading. Used to calculate the rotational offset between
+ * sensor events.
+ */
+ private float mPreviousSensorY = 0f;
+
+ /**
+ * MediaPlayer that plays the background music.
+ */
+ private MediaPlayer mBackgroundMusic;
+
+ /**
+ * Index of loaded sound effect in sound pool for small bounce.
+ */
+ private int mSoundBounceSmall = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for medium bounce.
+ */
+ private int mSoundBounceMed = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for large bounce.
+ */
+ private int mSoundBounceLarge = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for ball in machine.
+ */
+ private int mSoundBallInMachine = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for failed ball.
+ */
+ private int mSoundBallFail = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for dropped ball.
+ */
+ private int mSoundBallDrop = -1;
+
+ /**
+ * Index of loaded sound effect in sound pool for game over.
+ */
+ private int mSoundGameOver = -1;
+
+ /**
+ * Scale down animation for level.
+ */
+ private Animation mAnimationScaleLevelDown;
+
+ /**
+ * Fading out animation for level.
+ */
+ private Animation mAnimationLevelFadeOut;
+
+ /**
+ * Scaling up animation for level.
+ */
+ private Animation mAnimationLevelScaleUp;
+
+ /**
+ * Outlet animation for balls.
+ */
+ private Animation mAnimationOutlet;
+
+ /**
+ * Alpha animation for timer updates.
+ */
+ private Animation mAnimationTimerAlpha;
+
+ /**
+ * View for end of level circle overlay.
+ */
+ private CircleView mEndLevelCircle;
+
+ /**
+ * View that shows the current level number.
+ */
+ private LevelTextView mLevelNumberText;
+
+ /**
+ * Sound pool from which all sounds are played back.
+ */
+ private SoundPool mSoundPool;
+
+ /**
+ * Holder for sound pool id to handle playbacks, connects and disconnects.
+ */
+ private final HashMap mSoundPoolId = new HashMap<>();
+
+ /**
+ * Number of balls left in the game.
+ */
+ private int mGameBallsLeft = 2;
+
+ /**
+ * Current play level. Zero indexed, first level is 0.
+ */
+ private int mCurrentLevelNum = 0;
+
+ /**
+ * View for the ball outlet at the top of the screen.
+ */
+ private View mGameOutlet;
+
+ /**
+ * Root view of the game layout.
+ */
+ private View mRootView;
+
+ /**
+ * Gumballs that are queued to be dropped through the outlet.
+ */
+ private Queue mGumballQueue;
+
+ /**
+ * The current, active gumball on screen.
+ */
+ private Gumball mCurrentGumball;
+
+ /**
+ * X position of outlet in the last animation.
+ */
+ private float mOutletPreviousXPos = 0;
+
+ /**
+ * Array of the ball indicator views at the bottom of the screen.
+ */
+ private ImageView mViewIndicators[] = new ImageView[6];
+
+ /**
+ * Number of gumballs collected in the current game.
+ */
+ private int mNumberCollected = 0;
+
+ /**
+ * Refresh rate for the game countdown timer.
+ *
+ * @see com.google.android.apps.santatracker.games.gumball.TiltGameFragment.GameCountdown
+ */
+ private int mFramesPerSecond = 60;
+
+ /**
+ * Time left in the current game. Value in milliseconds.
+ */
+ private long mTimeLeftInMillis = MatchingGameConstants.GUMBALL_INIT_TIME;
+
+ /**
+ * Countdown timer for the current game.
+ */
+ private GameCountdown mCountDownTimer = null;
+
+ /**
+ * Countdown timer text.
+ */
+ private TextView mViewCountdown;
+
+ /**
+ * Score text.
+ */
+ private TextView mViewScore;
+
+ /**
+ * Total score of current game.
+ */
+ private int mMatchScore = 0;
+
+ /**
+ * Number of balls that have respawned in the current level. Used to calculate the total
+ * game score.
+ */
+ private int mCountLevelBallRespawns = 0;
+
+ /**
+ * Flag indicating if the game is paused.
+ */
+ private boolean wasPaused = false;
+
+ private ImageView mViewPlayButton;
+
+ private ImageView mViewPauseButton;
+
+ private ImageButton mViewBigPlayButton;
+
+ private ImageView mViewCancelBar;
+
+ private ImageView mViewInviteButton;
+
+ private View mViewMatchPauseOverlay;
+
+ private View mViewPlayAgainBackground;
+
+ private View mViewPlayAgainMain;
+
+ private Button mViewPlayAgainButton;
+
+ private TextView mViewPlayAgainScore;
+
+ private TextView mViewPlayAgainLevel;
+
+ private Animation mAnimationPlayAgainBackground;
+
+ private Animation mAnimationPlayAgainMain;
+
+ /**
+ * Display offset on X axis for outlet in pixels.
+ */
+ private int mOutletOffset;
+
+ /**
+ * View that displays the instructions from {@link #mDrawableTransition}
+ */
+ private ImageView mViewInstructions;
+
+ /**
+ * Drawable that contains all images for the instructions.
+ */
+ private AnimationDrawable mDrawableTransition;
+
+ private SharedPreferences mSharedPreferences;
+
+ private ImageView mViewGPlusSignIn;
+
+ private View mViewGPlusLayout;
+
+ private ImageButton mViewMainMenuButton;
+
+ private AppInvitesFragment mInvitesFragment;
+
+ /**
+ * Gets an instance of this fragment
+ */
+ public static TiltGameFragment newInstance() {
+ TiltGameFragment fragment = new TiltGameFragment();
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mRootView = inflater.inflate(R.layout.fragment_gumball, container, false);
+ mRootView.setKeepScreenOn(true);
+
+ // Use a lower resolution background image to conserve memory below ICS
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ View matchScoreLayout = mRootView.findViewById(R.id.tilt_score_layout);
+ matchScoreLayout.setBackgroundResource(R.drawable.score_background_gingerbread);
+ }
+
+ mViewPlayAgainScore = (TextView) mRootView.findViewById(R.id.play_again_score);
+ mViewPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainLevel = (TextView) mRootView.findViewById(R.id.play_again_level);
+ mViewPlayAgainLevel.setText(String.valueOf(mCurrentLevelNum));
+ mViewPlayAgainBackground = mRootView.findViewById(R.id.play_again_bkgrd);
+ mViewPlayAgainMain = mRootView.findViewById(R.id.play_again_main);
+ mViewPlayAgainButton = (Button) mRootView.findViewById(R.id.play_again_btn);
+ mViewPlayAgainButton.setOnClickListener(this);
+
+ mViewGPlusSignIn = (ImageView) mRootView.findViewById(R.id.gplus_button);
+ mViewGPlusSignIn.setOnClickListener(this);
+ mViewGPlusLayout = mRootView.findViewById(R.id.play_again_gplus);
+ mViewGPlusLayout.setVisibility(View.GONE);
+
+ // Initialise all animations
+ // Construct an animation to blink the timer indefinitely
+ mAnimationTimerAlpha = new AlphaAnimation(0.0f, 1.0f);
+ mAnimationTimerAlpha.setDuration(1000);
+ mAnimationTimerAlpha.setRepeatMode(Animation.REVERSE);
+ mAnimationTimerAlpha.setRepeatCount(Animation.INFINITE);
+
+ // Load all other animations
+ mAnimationPlayAgainBackground = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.play_again_bkgrd_anim);
+ mAnimationPlayAgainBackground.setFillAfter(true);
+ mAnimationPlayAgainBackground.setAnimationListener(this);
+ mAnimationPlayAgainMain = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.play_again_main_anim);
+ mAnimationPlayAgainMain.setFillAfter(true);
+ mAnimationPlayAgainMain.setAnimationListener(this);
+ mAnimationScaleLevelDown = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.scale_level_anim_down);
+ mAnimationScaleLevelDown.setAnimationListener(this);
+ mAnimationLevelFadeOut = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.level_fade_out_anim);
+ mAnimationLevelFadeOut.setAnimationListener(this);
+ mAnimationLevelScaleUp = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.scale_up_level_anim);
+ mAnimationLevelScaleUp.setAnimationListener(this);
+
+ mViewMainMenuButton = (ImageButton) mRootView.findViewById(R.id.main_menu_button);
+ mViewMainMenuButton.setVisibility(View.GONE);
+ mViewMainMenuButton.setOnClickListener(this);
+
+ // App Invites Button
+ mViewInviteButton = (ImageView) mRootView.findViewById(R.id.invite_button);
+ mViewInviteButton.setVisibility(View.GONE);
+ mViewInviteButton.setOnClickListener(this);
+
+ mGameOutlet = mRootView.findViewById(R.id.tiltGameOutlet);
+ mOutletOffset = getResources().getInteger(R.integer.outlet_offset);
+
+ mViewIndicators[0] = (ImageView) mRootView.findViewById(R.id.indicator1);
+ mViewIndicators[1] = (ImageView) mRootView.findViewById(R.id.indicator2);
+ mViewIndicators[2] = (ImageView) mRootView.findViewById(R.id.indicator3);
+ mViewIndicators[3] = (ImageView) mRootView.findViewById(R.id.indicator4);
+ mViewIndicators[4] = (ImageView) mRootView.findViewById(R.id.indicator5);
+ mViewIndicators[5] = (ImageView) mRootView.findViewById(R.id.indicator6);
+ mViewCountdown = (TextView) mRootView.findViewById(R.id.tiltTimer);
+
+ mLevelNumberText = (LevelTextView) mRootView.findViewById(R.id.tilt_end_level_number);
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle = (CircleView) mRootView.findViewById(R.id.tilt_end_level_circle);
+ mEndLevelCircle.setVisibility(View.GONE);
+
+ mViewPlayButton = (ImageView) mRootView.findViewById(R.id.tilt_play_button);
+ mViewPlayButton.setOnClickListener(this);
+ mViewPlayButton.setVisibility(View.GONE);
+ mViewPauseButton = (ImageView) mRootView.findViewById(R.id.tilt_pause_button);
+ mViewPauseButton.setOnClickListener(this);
+ mViewPauseButton.setVisibility(View.VISIBLE);
+ mViewMatchPauseOverlay = mRootView.findViewById(R.id.tilt_pause_overlay);
+ mViewMatchPauseOverlay.setVisibility(View.GONE);
+ mViewBigPlayButton = (ImageButton) mRootView.findViewById(R.id.tilt_big_play_button);
+ mViewBigPlayButton.setOnClickListener(this);
+ mViewCancelBar = (ImageView) mRootView.findViewById(R.id.tilt_cancel_bar);
+ mViewCancelBar.setOnClickListener(this);
+ mViewCancelBar.setVisibility(View.GONE);
+
+ mViewScore = (TextView) mRootView.findViewById(R.id.tilt_score);
+ mViewScore.setText(String.valueOf(mMatchScore));
+
+ mGameView = (TiltGameView) mRootView.findViewById(R.id.tiltGameView);
+
+ // Create the Box2D physics world.
+ mWorld = new PhysicsWorld();
+ Vec2 gravity = new Vec2(0.0f, 0.0f);
+ mWorld.create(gravity);
+ mGameView.setModel(mWorld);
+ mWorld.getWorld().setContactListener(this);
+
+ mGumballQueue = new LinkedList<>();
+
+ // Initialise the sound pool and audio playback
+ mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
+ mSoundBounceSmall = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_1, 1);
+ mSoundBounceMed = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_2, 1);
+ mSoundBounceLarge = mSoundPool.load(getActivity(), R.raw.gbg_ball_bounce_3, 1);
+ mSoundBallInMachine = mSoundPool.load(getActivity(), R.raw.gbg_ball_into_machine, 1);
+ mSoundBallFail = mSoundPool.load(getActivity(), R.raw.gbg_ball_fall_out, 1);
+ mSoundBallDrop = mSoundPool.load(getActivity(), R.raw.gbg_new_ball_bounce_drop, 1);
+ mSoundGameOver = mSoundPool.load(getActivity(), R.raw.gameover, 1);
+
+
+ // Display the instructions if they haven't been seen before
+ mSharedPreferences = getActivity().getSharedPreferences(MatchingGameConstants.PREFERENCES_FILENAME, Context.MODE_PRIVATE);
+ if (!mSharedPreferences.getBoolean(MatchingGameConstants.GUMBALL_INSTRUCTIONS_VIEWED, false)) {
+ mDrawableTransition = new AnimationDrawable();
+ mDrawableTransition.addFrame(getResources().getDrawable(R.drawable.instructions_shake_1), 300);
+ mDrawableTransition.addFrame(getResources().getDrawable(R.drawable.instructions_shake_2), 300);
+ mDrawableTransition.addFrame(getResources().getDrawable(R.drawable.instructions_shake_3), 300);
+ mDrawableTransition.setOneShot(false);
+ mViewInstructions = (ImageView) mRootView.findViewById(R.id.instructions);
+
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ mViewInstructions.setImageResource(R.drawable.instructions_shake_1);
+ } else {
+ mViewInstructions.setImageDrawable(mDrawableTransition);
+ mViewInstructions.post(new Runnable() {
+ public void run() {
+ mDrawableTransition.start();
+ }
+ });
+ }
+
+ // Hide the instructions after 2 seconds
+ mViewInstructions.postDelayed(new HideInstructionsRunnable(), 2200);
+ }
+
+ return mRootView;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mInvitesFragment = AppInvitesFragment.getInstance(getActivity());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Resume the game play if the game was not paused
+ if (!wasPaused) {
+ mRotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ SensorManager sensorManager = (SensorManager) getActivity()
+ .getSystemService(Activity.SENSOR_SERVICE);
+ Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
+ }
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+ }
+
+ // Start the game loop if it is not initialised yet
+ if (mGameThread == null) {
+ mGameThread = new Runnable() {
+ public void run() {
+ synchronized (mWorld) {
+ if (!wasPaused) {
+ if (mCurrentLevelNum == 0) {
+ mCurrentLevelNum++;
+ loadLevel(mCurrentLevelNum);
+ }
+ mWorld.update();
+ mGameView.invalidate();
+ }
+ }
+ getActivity().getWindow().getDecorView().postDelayed(mGameThread, 10);
+ }
+ };
+ }
+ getActivity().getWindow().getDecorView().postDelayed(mGameThread, 1000);
+
+ loadBackgroundMusic();
+ updateSignInButtonVisibility();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseGame();
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ }
+ getActivity().getWindow().getDecorView().removeCallbacks(mGameThread);
+ }
+
+ private void loadBackgroundMusic() {
+ mBackgroundMusic = MediaPlayer.create(getActivity(), R.raw.santatracker_musicloop);
+ mBackgroundMusic.setLooping(true);
+ mBackgroundMusic.setVolume(.2f, .2f);
+ mBackgroundMusic.start();
+ }
+
+ /**
+ * Hide the sign in button if sign in was successful.
+ */
+ public void onSignInSucceeded() {
+ setSignInButtonVisibility(false);
+ }
+
+ public void onSignInFailed() {
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.equals(mViewPauseButton)) {
+ // Pause the game
+ pauseGame();
+ } else if (view.equals(mViewPlayButton) || view.equals(mViewBigPlayButton)) {
+ // Continue the game
+ unPauseGame();
+ } else if (view.equals(mViewPlayAgainButton)) {
+ // Reload the background music for a new game
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ }
+ loadBackgroundMusic();
+
+ // Reset the game variables
+ mCurrentLevelNum = 0;
+ mTimeLeftInMillis = MatchingGameConstants.GUMBALL_INIT_TIME;
+ mMatchScore = 0;
+ mViewScore.setText(String.valueOf(mMatchScore));
+ wasPaused = false;
+
+ // Hide the pause screen
+ mViewPlayAgainBackground.clearAnimation();
+ mViewPlayAgainMain.clearAnimation();
+ mViewPlayAgainBackground.setVisibility(View.GONE);
+ mViewPlayAgainMain.setVisibility(View.GONE);
+ mViewGPlusLayout.setVisibility(View.GONE);
+ mViewMainMenuButton.setVisibility(View.GONE);
+ mViewInviteButton.setVisibility(View.GONE);
+ } else if (view.equals(mViewGPlusSignIn)) {
+ // Start sign-in flow.
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.startSignIn();
+ }
+ } else if (view.equals(mViewCancelBar) || (view.equals(mViewMainMenuButton))) {
+ // Exit and return to previous Activity.
+ returnToBackClass();
+ } else if (view.equals(mViewInviteButton)) {
+ // Send App Invite
+ mInvitesFragment.sendGameInvite(
+ getString(R.string.gumball),
+ getString(R.string.gumball_game_id),
+ mMatchScore);
+ }
+
+ }
+
+ private void returnToBackClass() {
+ getActivity().finish();
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ float x, y;
+ if (getActivity() != null) {
+ // Store the current screen rotation (used to offset the readings of the sensor).
+ mRotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ }
+
+ // Handle screen rotations by interpreting the sensor readings here
+ if (mRotation == Surface.ROTATION_0) {
+ x = -event.values[0];
+ y = -event.values[1];
+ } else if (mRotation == Surface.ROTATION_90) {
+ x = event.values[1];
+ y = -event.values[0];
+ } else if (mRotation == Surface.ROTATION_180) {
+ x = event.values[0];
+ y = event.values[1];
+ } else {
+ x = -event.values[1];
+ y = event.values[0];
+ }
+ // keep y low to simulate gravity
+ if (mPreviousSensorY == 0f) {
+ mPreviousSensorY = -9;
+ } else if (mPreviousSensorY > y) {
+ mPreviousSensorY = y;
+ }
+ // restrict x to ~+-45 degrees
+ if (x > 1.7) {
+ x = 2;
+ } else if (x < -1.7) {
+ x = -2;
+ }
+ mWorld.getWorld().setGravity(new Vec2(x, mPreviousSensorY));
+ }
+
+ @Override
+ public void beginContact(Contact contact) {
+
+ }
+
+ /**
+ * Handle contact with objects in the Box 2D world.
+ * Here the main game logic is implemented: When a ball hits the bottom pipe, it is removed
+ * and the next level or ball is started.
+ * When the ball goes over the edge, it is removed and a new ball is dropped from the pipe
+ * again.
+ */
+ @Override
+ public void endContact(Contact contact) {
+ // If the gumball goes in the pipe, remove it from the scene (Case 1/2)
+ if (contact.getFixtureA().getBody().getUserData() != null && !(contact.getFixtureA()
+ .getBody().getUserData() instanceof Gumball) && (
+ contact.getFixtureA().getBody().getUserData().equals(TiltGameView.PIPE_BOTTOM)
+ || contact.getFixtureA().getBody().getUserData()
+ .equals(TiltGameView.PIPE_SIDES))) {
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureB().getBody());
+ mSoundPoolId
+ .remove(((Gumball) contact.getFixtureB().getBody().getUserData()).mSoundPoolId);
+ onBallInPipe();
+ } else if (contact.getFixtureB().getBody().getUserData() != null && !(contact.getFixtureB()
+ .getBody().getUserData() instanceof Gumball) && (
+ // If the gumball goes in the pipe, remove it from the scene (Case 2/2)
+ contact.getFixtureA().getBody().getUserData().equals(TiltGameView.PIPE_BOTTOM)
+ || contact.getFixtureA().getBody().getUserData()
+ .equals(TiltGameView.PIPE_SIDES))) {
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureA().getBody());
+ mSoundPoolId
+ .remove(((Gumball) contact.getFixtureA().getBody().getUserData()).mSoundPoolId);
+ onBallInPipe();
+ } else if (contact.getFixtureA().getBody().getUserData() != null && !(contact.getFixtureA()
+ .getBody().getUserData() instanceof Gumball) && contact.getFixtureA().getBody()
+ .getUserData().equals(TiltGameView.GAME_FLOOR)) {
+ // If the gumball goes over the edge, remove it and respawn (Case 1/2)
+ Gumball gumball = ((Gumball) contact.getFixtureB().getBody().getUserData());
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureB().getBody());
+ mSoundPoolId.remove(gumball.mSoundPoolId);
+ mSoundPool.play(mSoundBallFail, 1, 1, 0, 0, 1.0f);
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+ moveOutlet((mCurrentGumball.mXInitPos));
+ mCountLevelBallRespawns++;
+ } else if (contact.getFixtureB().getBody().getUserData() != null && !(contact.getFixtureB()
+ .getBody().getUserData() instanceof Gumball) && contact.getFixtureB().getBody()
+ .getUserData().equals(TiltGameView.GAME_FLOOR)) {
+ // If the gumball goes over the edge, remove it and respawn (Case 2/2)
+ Gumball gumball = ((Gumball) contact.getFixtureB().getBody().getUserData());
+ mWorld.mBodiesToBeRemoved.add(contact.getFixtureA().getBody());
+ mSoundPoolId.remove(gumball.mSoundPoolId);
+ mSoundPool.play(mSoundBallFail, 1, 1, 0, 0, 1.0f);
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+ moveOutlet((mCurrentGumball.mXInitPos));
+ mCountLevelBallRespawns++;
+ }
+ }
+
+ /**
+ * Successfully dropped a ball in the pipe.
+ * Add the next ball and go to the next level if no balls are left in this level.
+ */
+ private void onBallInPipe() {
+ mSoundPool.play(mSoundBallInMachine, 1, 1, 0, 0, 1.0f);
+ mGameBallsLeft--;
+ mNumberCollected++;
+ changeIndicator();
+ mMatchScore += 50 * Math.max(1f, (mCurrentLevelNum - mCountLevelBallRespawns));
+ mViewScore.setText(String.valueOf(mMatchScore));
+ if (mGameBallsLeft == 0 && mViewPlayAgainBackground.getVisibility() != View.VISIBLE) {
+ // No balls are left in this level, go to the next one
+ mCurrentLevelNum++;
+ mLevelNumberText.setLevelNumber(mCurrentLevelNum);
+ mLevelNumberText.startAnimation(mAnimationLevelScaleUp);
+ mEndLevelCircle.startAnimation(mAnimationScaleLevelDown);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jbox2d.callbacks.ContactListener#postSolve(org.jbox2d.dynamics.contacts
+ * .Contact, org.jbox2d.callbacks.ContactImpulse)
+ */
+
+ /**
+ * Play a sound on impact (when a ball is dropped).
+ * The sound depends on the severity of the impact.
+ *
+ * @see #playBounceSound(float)
+ */
+ @Override
+ public void postSolve(Contact contact, ContactImpulse impulse) {
+ // Get both collision objects
+ Object dataA = contact.getFixtureA().getBody().getUserData();
+ Object dataB = contact.getFixtureB().getBody().getUserData();
+
+ // Check if one of the objects is NOT a gumball, but a candy cane.
+ boolean hitCane = false;
+ if (dataA != null && !(dataA instanceof Gumball)
+ && (Integer) dataA > TiltGameView.GUMBALL_PURPLE) {
+ hitCane = true;
+ } else if (dataB != null && !(dataB instanceof Gumball)
+ && (Integer) dataB > TiltGameView.GUMBALL_PURPLE) {
+ hitCane = true;
+ }
+
+ if (hitCane && impulse.normalImpulses[0] > 80) {
+ playBounceSound(impulse.normalImpulses[0]);
+ }
+ }
+
+ /**
+ * Plays a 'bounce' sound through the sound pool, depending on the impulse.
+ */
+ private void playBounceSound(float impulse) {
+ if (impulse > 80) {
+ mSoundPool.play(mSoundBounceLarge, 1, 1, 0, 0, 1.0f);
+ } else if (impulse > 60) {
+ mSoundPool.play(mSoundBounceMed, 1, 1, 0, 0, 1.0f);
+ } else if (impulse > 30) {
+ mSoundPool.play(mSoundBounceSmall, 1, 1, 0, 0, 1.0f);
+ }
+ }
+
+
+ @Override
+ public void preSolve(Contact contact, Manifold arg1) {
+
+ }
+
+ /**
+ * Add a gumball to the game and play the ball drop sound.
+ */
+ private void addGumball(float xPos, float yPos) {
+ Gumball gumball = new Gumball();
+ gumball.mXInitPos = xPos;
+ gumball.mYInitPos = yPos;
+ gumball.mSoundPoolId = UUID.randomUUID();
+ mSoundPoolId.put(gumball.mSoundPoolId, false);
+ mGameView.addGumball(gumball);
+ mSoundPool.play(mSoundBallDrop, 1, 1, 0, 0, 1);
+ }
+
+ private JSONObject readLevelFile(int levelNumber) throws IOException, JSONException {
+ // load the appropriate levels file from a raw resource.
+ InputStream is = getResources()
+ .openRawResource(Utils.getLevelRawFile(mCurrentLevelNum));
+ int size = is.available();
+ byte[] buffer = new byte[size];
+ is.read(buffer);
+ is.close();
+ String json = new String(buffer, "UTF-8");
+ JSONObject level = new JSONObject(json);
+
+ return level;
+ }
+ /**
+ * Loads a level from the levels json file and sets up the game world.
+ */
+ private void loadLevel(int levelNumber) {
+
+ // Reset the current game state
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ mCountLevelBallRespawns = 0;
+ mNumberCollected = 0;
+ mViewPlayAgainLevel.setText(String.valueOf(levelNumber));
+ Body body = mWorld.getWorld().getBodyList();
+ while (body != null) {
+ if (body.m_userData == null) {
+ body = body.getNext();
+ continue;
+ }
+ mWorld.mBodiesToBeRemoved.add(body);
+ body = body.getNext();
+ }
+ mWorld.getWorld().step(1.0f / 60.0f, 10, 10);
+
+ try {
+ // Read the level file and extract the candy cane positions
+ JSONObject level = readLevelFile(levelNumber);
+ JSONArray canes = level.getJSONArray("candycanes");
+
+ for (int i = 0; i < canes.length(); i++) {
+ JSONObject canePart = canes.getJSONObject(i);
+ int type = canePart.getInt("type");
+ float xPos = (float) canePart.getDouble("xPos");
+ float yPos = (float) canePart.getDouble("yPos");
+ // Add the candy cane to the game world, the values represent the
+ mWorld.addItem(xPos, yPos, Edges.getEdges(type), WORLD_OBJECT_BOUNCE, type,
+ WORLD_OBJECT_DENSITY, WORLD_OBJECT_FRICTION,
+ BodyType.STATIC);
+ }
+
+ // Add the sides and floor to the game world to catch dropped balls.
+ // Note that the WORLD_FRICTION is used as the bounce rate of the floors.
+ mWorld.addItem(WORLD_FLOOR_X, WORLD_FLOOR_Y, Edges.getPipeSideEdges(),
+ WORLD_OBJECT_BOUNCE, TiltGameView.PIPE_SIDES,
+ WORLD_OBJECT_DENSITY, WORLD_OBJECT_FRICTION, BodyType.STATIC);
+ mWorld.addFloor(WORLD_FLOOR_X, WORLD_FLOOR_Y, TiltGameView.GAME_FLOOR,
+ WORLD_OBJECT_DENSITY, WORLD_OBJECT_FRICTION, WORLD_FLOOR_FRICTION,
+ BodyType.STATIC);
+ mWorld.addPipeBottom(WORLD_FLOOR_X, WORLD_FLOOR_Y, TiltGameView.PIPE_BOTTOM,
+ WORLD_OBJECT_DENSITY, WORLD_OBJECT_FRICTION, WORLD_FLOOR_FRICTION,
+ BodyType.STATIC);
+
+ // Add the gumballs
+ JSONArray gumballs = level.getJSONArray("gumballs");
+ mGameBallsLeft = gumballs.length();
+ setIndicators(mGameBallsLeft);
+ for (int j = 0; j < gumballs.length(); j++) {
+ JSONObject gumball = gumballs.getJSONObject(j);
+ float xPos = (float) gumball.getDouble("xPos");
+ float yPos = (float) gumball.getDouble("yPos");
+ Gumball gumballObject = new Gumball();
+ gumballObject.mXInitPos = xPos;
+ gumballObject.mYInitPos = yPos;
+ mGumballQueue.add(gumballObject);
+ }
+ mCurrentGumball = mGumballQueue.poll();
+
+ // Start the timer
+ if (mCurrentGumball != null) {
+ if (mCurrentLevelNum > 1) {
+ // Do not include gumball dropping time in countdown calculation.
+ mTimeLeftInMillis += MatchingGameConstants.GUMBALL_ADDED_TIME;
+ }
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+
+ // Move the outlet to its initial position
+ moveOutlet((mCurrentGumball.mXInitPos));
+ }
+ } catch (IOException e) {
+ } catch (JSONException e) {
+ }
+
+ }
+
+ /**
+ * Update the state of the indicators at the bottom of the screen to the number of balls
+ * collected.
+ */
+ private void setIndicators(int numGumballs) {
+ for(int i=0; i < mViewIndicators.length ; i++){
+ int stateResource = R.drawable.gbg_gumball_indicator_collected_disabled;
+ if(i+1 <= numGumballs){
+ stateResource = R.drawable.gbg_gumball_indicator_pending;
+ }
+ mViewIndicators[i].setImageResource(stateResource);
+ }
+ }
+
+
+ /**
+ * Mark the last indicator for which a ball was collected in the 'collected' state.
+ */
+ private void changeIndicator() {
+ mViewIndicators[mNumberCollected - 1].setImageResource(
+ R.drawable.gbg_gumball_indicator_collected);
+ }
+
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ if (animation == mAnimationScaleLevelDown) {
+ // After the level scale down animation, fade out the level number and end circle
+ mLevelNumberText.startAnimation(mAnimationLevelFadeOut);
+ mEndLevelCircle.startAnimation(mAnimationLevelFadeOut);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // After the level fade out animation reset and hide all other end level views
+ mEndLevelCircle.clearAnimation();
+ mLevelNumberText.clearAnimation();
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle.setVisibility(View.GONE);
+ } else if (animation == mAnimationOutlet) {
+ // After the outlet has moved to the correct position, add gumball
+ addGumball(mCurrentGumball.mXInitPos, mCurrentGumball.mYInitPos);
+ if (mGumballQueue.peek() != null) {
+ // Move it to the next position if there is a gumball left in the queue
+ mCurrentGumball = mGumballQueue.poll();
+ moveOutlet(mCurrentGumball.mXInitPos);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation arg0) {
+ // do nothing
+
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ if (animation == mAnimationScaleLevelDown) {
+ // Show the circle level end and level text views when the animation starts
+ mEndLevelCircle.setVisibility(View.VISIBLE);
+ mLevelNumberText.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // Load the next level after the end level animation is over
+ loadLevel(mCurrentLevelNum);
+ } else if (animation == mAnimationPlayAgainBackground) {
+ // Show the 'play again' screen when the animation starts and cancel the timer
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ } else if (animation == mAnimationPlayAgainMain) {
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ }
+ }
+
+ /**
+ * Set the visibility of the sign in button if the user is not already signed in.
+ */
+ private void setSignInButtonVisibility(boolean show) {
+ mViewGPlusLayout.setVisibility(show && !Utils.isSignedIn(this) ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Hide the sign in button when the user signs in and the button is still visible on screen.
+ */
+ private void updateSignInButtonVisibility() {
+ if (mViewGPlusLayout.getVisibility() == View.VISIBLE && Utils.isSignedIn(this)) {
+ setSignInButtonVisibility(false);
+ }
+ }
+
+ /**
+ * Start an animation to move the outlet to the x position in pixels.
+ */
+ private void moveOutlet(float xPos) {
+ float scale = mRootView.getWidth() / 10.0f;
+ mAnimationOutlet = new TranslateAnimation(mOutletPreviousXPos, (scale * xPos) - mOutletOffset, 0, 0);
+ mAnimationOutlet.setDuration(700);
+ mAnimationOutlet.setFillAfter(true);
+ mAnimationOutlet.setStartOffset(400);
+ mAnimationOutlet.setAnimationListener(this);
+ mGameOutlet.startAnimation(mAnimationOutlet);
+ mOutletPreviousXPos = (scale * xPos) - mOutletOffset;
+ }
+
+ /**
+ * Countdown for the main game.
+ * Updates the countdown on screen and stops the game when the timer runs out.
+ */
+ public class GameCountdown {
+
+ private Boolean animationStarted = false;
+
+ private final long mMillisDuration;
+
+ private final long mMillisTickDuration;
+
+ private long mTicksLeft;
+
+ private boolean mStarted = false;
+
+ private long mSecondsTextValue = -1;
+
+ /**
+ * @param framesPerSecond assumed frame rate
+ * @param millisInFuture duration of game at this frame rate
+ */
+ public GameCountdown(int framesPerSecond, long millisInFuture) {
+ mMillisDuration = millisInFuture;
+ mMillisTickDuration = 1000 / framesPerSecond;
+ mTicksLeft = mMillisDuration / mMillisTickDuration;
+ }
+
+ /**
+ * Stop the timer.
+ */
+ public void cancel() {
+ mTicksLeft = 0;
+ mStarted = false;
+ }
+
+ /**
+ * Starts the timer.
+ */
+ public void start() {
+ mStarted = true;
+ mSecondsTextValue = -1;
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(mTicksLeft * mMillisTickDuration);
+ if (seconds >= 6) {
+ animationStarted = false;
+ mViewCountdown.clearAnimation();
+ mViewCountdown.setTextColor(Color.WHITE);
+ mViewCountdown.setTypeface(Typeface.DEFAULT);
+ }
+ }
+
+ /**
+ * Update the displayed timer.
+ * When the timer is below 6s the text color changes to red.
+ */
+ public void tick() {
+ if (mStarted) {
+ --mTicksLeft;
+ mTimeLeftInMillis = mTicksLeft * mMillisTickDuration;
+ if (mTimeLeftInMillis < 6000 && !animationStarted) {
+ animationStarted = true;
+ mViewCountdown.setTextColor(Color.RED);
+ mViewCountdown.setTypeface(Typeface.DEFAULT_BOLD);
+ mViewCountdown.clearAnimation();
+ mViewCountdown.startAnimation(mAnimationTimerAlpha);
+ }
+ if (mSecondsTextValue != mTimeLeftInMillis / 1000) {
+ mViewCountdown.setText(
+ String.format("%d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(mTimeLeftInMillis),
+ TimeUnit.MILLISECONDS.toSeconds(mTimeLeftInMillis)));
+ mSecondsTextValue = mTimeLeftInMillis / 1000;
+ }
+ if (mTimeLeftInMillis == 0) {
+ finished();
+ }
+ }
+ }
+
+ /**
+ * Shut down the count down timer.
+ * Cancel all pending animations and display the 'play again' screen.
+ */
+ private void finished() {
+ mViewCountdown.clearAnimation();
+ animationStarted = false;
+ mViewCountdown.setTextColor(Color.WHITE);
+ mViewCountdown.setTypeface(Typeface.DEFAULT);
+ if (mViewPlayAgainBackground.getVisibility() != View.VISIBLE && !wasPaused) {
+ wasPaused = true;
+ submitScore(MatchingGameConstants.LEADERBOARDS_GUMBALL, mMatchScore);
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ mBackgroundMusic = null;
+ }
+ mViewPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.startAnimation(mAnimationPlayAgainBackground);
+ mViewPlayAgainMain.startAnimation(mAnimationPlayAgainMain);
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ mViewMainMenuButton.setVisibility(View.VISIBLE);
+ mViewInviteButton.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ mSoundPool.play(mSoundGameOver, .2f, .2f, 0, 0, 1.0f);
+ }
+
+ cancel();
+ }
+ }
+
+ /**
+ * Pause the game when the back key is pressed.
+ */
+ public void onBackKeyPressed() {
+ if (mViewPlayAgainMain.getVisibility() == View.VISIBLE) {
+ returnToBackClass();
+ } else {
+ if (mViewPauseButton.getVisibility() != View.GONE) {// check if already handled
+ pauseGame();
+ } else {
+ unPauseGame();
+ }
+ }
+ }
+
+ /**
+ * Pause the game and display the pause game screen.
+ */
+ private void pauseGame() {
+ mViewPauseButton.setVisibility(View.GONE);
+ mViewPlayButton.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ wasPaused = true;
+ }
+ mViewMatchPauseOverlay.setVisibility(View.VISIBLE);
+ mViewCancelBar.setVisibility(View.VISIBLE);
+
+ SensorManager sensorManager = (SensorManager) getActivity()
+ .getSystemService(Activity.SENSOR_SERVICE);
+ sensorManager.unregisterListener(this);
+ if (Utils.hasKitKat()) {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(getActivity().getWindow());
+ }
+ }
+
+ /**
+ * Continue the paused game.
+ * Restart the countdown timer and hide the pause game screen.
+ */
+ private void unPauseGame() {
+ mViewPauseButton.setVisibility(View.VISIBLE);
+ mViewPlayButton.setVisibility(View.GONE);
+ mViewMatchPauseOverlay.setVisibility(View.GONE);
+ mViewCancelBar.setVisibility(View.GONE);
+ mCountDownTimer = new GameCountdown(mFramesPerSecond, mTimeLeftInMillis);
+ mCountDownTimer.start();
+ mGameView.setGameCountDown(mCountDownTimer);
+ wasPaused = false;
+ SensorManager sensorManager = (SensorManager) getActivity()
+ .getSystemService(Activity.SENSOR_SERVICE);
+ Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (sensor != null) {
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
+ }
+ if (Utils.hasKitKat()) {
+ ImmersiveModeHelper.setImmersiveSticky(getActivity().getWindow());
+ }
+ }
+
+ /**
+ * Submit score to play games services
+ */
+ private void submitScore(int resId, int score) {
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.postSubmitScore(resId, score);
+ }
+ }
+
+ /**
+ * Hide the instructions and mark them as viewed.
+ */
+ private class HideInstructionsRunnable implements Runnable {
+
+ @Override
+ public void run() {
+ mDrawableTransition.stop();
+ wasPaused = false;
+ mViewInstructions.setVisibility(View.GONE);
+ Editor edit = mSharedPreferences.edit();
+ edit.putBoolean(MatchingGameConstants.GUMBALL_INSTRUCTIONS_VIEWED, true);
+ edit.apply();
+ }
+
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameView.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameView.java
new file mode 100644
index 000000000..77d893533
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/TiltGameView.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+import com.google.android.apps.santatracker.R;
+
+import org.jbox2d.collision.shapes.CircleShape;
+import org.jbox2d.collision.shapes.EdgeShape;
+import org.jbox2d.collision.shapes.Shape;
+import org.jbox2d.common.Vec2;
+import org.jbox2d.dynamics.Body;
+import org.jbox2d.dynamics.BodyType;
+import org.jbox2d.dynamics.Fixture;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Random;
+
+
+/**
+ * Custom view which contains the elements used in the physics word.
+ * It handles the painting of all bitmaps for the game, including all levels (canes),
+ * the pipe and gumballs.
+ */
+public class TiltGameView extends View {
+
+ public static final float GUMBALL_DENSITY = 185.77f;
+ public static final float GUMBALL_RADIUS = 0.258f;
+ public static final float GUMBALL_BOUNCE = 0.2f;
+ public static final float GUMBALL_FRICTION = 0.8f;
+ /**
+ * The physics world for the gumball game.
+ */
+ private PhysicsWorld mWorld;
+
+ /**
+ * Bitmaps for all elements on screen.
+ */
+ private Bitmap mGumballBlue;
+ private Bitmap mGumballYellow;
+ private Bitmap mGumballRed;
+ private Bitmap mGumballGreen;
+ private Bitmap mGumballOrange;
+ private Bitmap mGumballPurple;
+ private Bitmap mCaneMainLong;
+ private Bitmap mCaneMainLongReverse;
+ private Bitmap mCaneMainMed;
+ private Bitmap mCaneMainMedReverse;
+ private Bitmap mCaneMainSmall;
+ private Bitmap mCaneMainSmallReverse;
+ private Bitmap mCaneMainTiny;
+ private Bitmap mCaneMainTinyReverse;
+ private Bitmap mCaneHook;
+ private Bitmap mCaneHookFlip;
+ private Bitmap mCaneHookReverse;
+ private Bitmap mCaneHookReverseFlip;
+ private Bitmap mCaneEnd;
+ private Bitmap mCaneEndFlip;
+ private Bitmap mCaneEndReverse;
+ private Bitmap mCaneEndReverseFlip;
+
+ private Bitmap mCaneMainSmallAngleNine;
+ private Bitmap mCaneMainSmallAngleSix;
+ private Bitmap mCaneMainSmallAngleTwelve;
+ private Bitmap mCaneMainReverseTinyAngleTwelve;
+ private Bitmap mCaneMainLargeAngleSix;
+ private Bitmap mCaneMainMedAngleSix;
+
+ /**
+ * Bitmap of a pipe where gumballs drop.
+ */
+ private Bitmap mPipeSides;
+
+ /**
+ * Default paint object that is used to draw all bitmaps to the screen.
+ */
+ private Paint mPaint = new Paint();
+
+ /**
+ * Identifiers for objects in the world.
+ */
+ public static final int GUMBALL_RED = 0;
+ public static final int GUMBALL_BLUE = 1;
+ public static final int GUMBALL_YELLOW = 2;
+ public static final int GUMBALL_GREEN = 3;
+ public static final int GUMBALL_ORANGE = 4;
+ public static final int GUMBALL_PURPLE = 5;
+ public static final int[] GUMBALLS = new int[]{GUMBALL_RED, GUMBALL_BLUE, GUMBALL_YELLOW,
+ GUMBALL_GREEN, GUMBALL_ORANGE, GUMBALL_PURPLE};
+
+ public static final int CANE_MAIN_LONG = 6;
+ public static final int CANE_MAIN_LONG_REVERSE = 7;
+ public static final int CANE_MAIN_MEDIUM = 8;
+ public static final int CANE_MAIN_MEDIUM_REVERSE = 9;
+ public static final int CANE_MAIN_SMALL = 10;
+ public static final int CANE_MAIN_SMALL_REVERSE = 11;
+ public static final int CANE_MAIN_TINY = 12;
+ public static final int CANE_MAIN_TINY_REVERSE = 13;
+ public static final int CANE_HOOK = 14;
+ public static final int CANE_HOOK_FLIP = 15;
+ public static final int CANE_HOOK_REVERSE = 16;
+ public static final int CANE_HOOK_REVERSE_FLIP = 17;
+ public static final int CANE_END = 18;
+ public static final int CANE_END_FLIP = 19;
+ public static final int CANE_END_REVERSE = 20;
+ public static final int CANE_END_REVERSE_FLIP = 21;
+
+ public static final int CANE_MAIN_SMALL_ANGLE_NINE = 22;
+ public static final int CANE_MAIN_SMALL_ANGLE_SIX = 23;
+ public static final int CANE_MAIN_SMALL_ANGLE_TWELVE = 24;
+ public static final int CANE_MAIN_REVERSE_TINY_ANGLE_SIX = 25;
+ public static final int CANE_MAIN_LARGE_ANGLE_SIX = 26;
+ public static final int CANE_MAIN_MED_ANGLE_SIX = 27;
+
+ public static final int PIPE_SIDES = -1;
+ /**
+ * Bottom of the pipe user data, this is separate from the side because the gumball is removed
+ * from the scene on collision with it.
+ */
+ public static final int PIPE_BOTTOM = -2;
+
+ public static final int GAME_FLOOR = -3;
+
+ private static Random sRandomGenerator = new Random();
+
+ private TiltGameFragment.GameCountdown mGameCountDown;
+
+ public TiltGameView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TiltGameView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ setClickable(true);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Load the bitmaps as soon as we have the size of the view to scale them appropriately.
+ if (mGumballBlue == null) {
+ loadBitmaps();
+ }
+ }
+
+ private void loadBitmaps() {
+ // set the bitmaps
+ Resources res = getResources();
+ int[] gumballBlue = {R.drawable.gbg_gumball_blue_1920, R.drawable.gbg_gumball_blue_1280,
+ R.drawable.gbg_gumball_blue_800, R.drawable.gbg_gumball_blue_480};
+ int[] gumballRed = {R.drawable.gbg_gumball_red_1920, R.drawable.gbg_gumball_red_1280,
+ R.drawable.gbg_gumball_red_800, R.drawable.gbg_gumball_red_480};
+ int[] gumballYellow = {R.drawable.gbg_gumball_yellow_1920,
+ R.drawable.gbg_gumball_yellow_1280, R.drawable.gbg_gumball_yellow_800,
+ R.drawable.gbg_gumball_yellow_480};
+ int[] gumballGreen = {R.drawable.gbg_gumball_green_1920,
+ R.drawable.gbg_gumball_green_1280, R.drawable.gbg_gumball_green_800,
+ R.drawable.gbg_gumball_green_480};
+ int[] gumballOrange = {R.drawable.gbg_gumball_orange_1920,
+ R.drawable.gbg_gumball_orange_1280, R.drawable.gbg_gumball_orange_800,
+ R.drawable.gbg_gumball_orange_480};
+ int[] gumballPurple = {R.drawable.gbg_gumball_purple_1920,
+ R.drawable.gbg_gumball_purple_1280, R.drawable.gbg_gumball_purple_800,
+ R.drawable.gbg_gumball_purple_480};
+ int[] caneMain = {R.drawable.gbg_candycane_main_1920,
+ R.drawable.gbg_candycane_main_1280, R.drawable.gbg_candycane_main_800,
+ R.drawable.gbg_candycane_main_480};
+ int[] caneMainReverse = {R.drawable.gbg_candycane_main_reverse_1920,
+ R.drawable.gbg_candycane_main_reverse_1280,
+ R.drawable.gbg_candycane_main_reverse_800,
+ R.drawable.gbg_candycane_main_reverse_480};
+ int[] caneHook = {R.drawable.gbg_candycane_hook_1920,
+ R.drawable.gbg_candycane_hook_1280, R.drawable.gbg_candycane_hook_800,
+ R.drawable.gbg_candycane_hook_480};
+ int[] caneHookReverse = {R.drawable.gbg_candycane_hook_reverse_1920,
+ R.drawable.gbg_candycane_hook_reverse_1280,
+ R.drawable.gbg_candycane_hook_reverse_800,
+ R.drawable.gbg_candycane_hook_reverse_480};
+ int[] caneEnd = {R.drawable.gbg_candycane_end_1920, R.drawable.gbg_candycane_end_1280,
+ R.drawable.gbg_candycane_end_800, R.drawable.gbg_candycane_end_480};
+ int[] caneEndReverse = {R.drawable.gbg_candycane_end_reverse_1920,
+ R.drawable.gbg_candycane_end_reverse_1280,
+ R.drawable.gbg_candycane_end_reverse_800,
+ R.drawable.gbg_candycane_end_reverse_480};
+ int[] pipes = {R.drawable.gbg_gumball_funnel_1920, R.drawable.gbg_gumball_funnel_1280,
+ R.drawable.gbg_gumball_funnel_800, R.drawable.gbg_gumball_funnel_480};
+ int[] caneMainAngleNine = {R.drawable.gbg_candycane_main_angle_nine,
+ R.drawable.gbg_candycane_main_angle_nine,
+ R.drawable.gbg_candycane_main_angle_nine,
+ R.drawable.gbg_candycane_main_angle_nine};
+ int[] caneMainAngleSix = {R.drawable.gbg_candycane_small_angle_six,
+ R.drawable.gbg_candycane_small_angle_six,
+ R.drawable.gbg_candycane_small_angle_six,
+ R.drawable.gbg_candycane_small_angle_six};
+ int[] caneMainAngleTwelve = {R.drawable.gbg_candycane_small_angle_twelve,
+ R.drawable.gbg_candycane_small_angle_twelve,
+ R.drawable.gbg_candycane_small_angle_twelve,
+ R.drawable.gbg_candycane_small_angle_twelve};
+ int[] caneMainTinyReverseAngleSix = {R.drawable.gbg_candycane_tiny_reverse_angle_six,
+ R.drawable.gbg_candycane_tiny_reverse_angle_six,
+ R.drawable.gbg_candycane_tiny_reverse_angle_six,
+ R.drawable.gbg_candycane_tiny_reverse_angle_six};
+ int[] caneMainLargeAngleSix = {R.drawable.gbg_candycane_large_angle_six,
+ R.drawable.gbg_candycane_large_angle_six,
+ R.drawable.gbg_candycane_large_angle_six,
+ R.drawable.gbg_candycane_large_angle_six};
+ int[] caneMainMedAngleSix = {R.drawable.gbg_candycane_med_angle_six,
+ R.drawable.gbg_candycane_med_angle_six, R.drawable.gbg_candycane_med_angle_six,
+ R.drawable.gbg_candycane_med_angle_six};
+ int[] sizes = {1920, 1280, 800, 480};
+
+ final int viewWidth = getWidth();
+ final int size = sizes[0];
+
+ mGumballBlue = resizeImage(res, gumballBlue[0], size, viewWidth, -360f, true, 1);
+ mGumballRed = resizeImage(res, gumballRed[0], size, viewWidth, -360f, true, 1);
+ mGumballYellow = resizeImage(res, gumballYellow[0], size, viewWidth, -360f, true, 1);
+ mGumballGreen = resizeImage(res, gumballGreen[0], size, viewWidth, -360f, true, 1);
+ mGumballOrange = resizeImage(res, gumballOrange[0], size, viewWidth, -360f, true, 1);
+ mGumballPurple = resizeImage(res, gumballPurple[0], size, viewWidth, -360f, true, 1);
+
+ mCaneMainLong = resizeImage(res, caneMain[0], size, viewWidth, 180f, false, 1);
+ mCaneMainLongReverse = resizeImage(res, caneMainReverse[0], size, viewWidth, 180f, false,
+ 1);
+ mCaneMainMed = resizeImage(res, caneMain[0], size, viewWidth, 180f, false, .75f);
+ mCaneMainMedReverse = resizeImage(res, caneMainReverse[0], size, viewWidth, 180f, false,
+ .75f);
+
+ mCaneMainSmall = resizeImage(res, caneMain[0], size, viewWidth, 180f, false, .50f);
+ mCaneMainSmallReverse = resizeImage(res, caneMainReverse[0], size, viewWidth, 180f,
+ false, .50f);
+ mCaneMainTiny = resizeImage(res, caneMain[0], size, viewWidth, 180f, false, .25f);
+ mCaneMainTinyReverse = resizeImage(res, caneMainReverse[0], size, viewWidth, 180f, false,
+ .25f);
+
+ mCaneMainSmallAngleNine = resizeImage(res, caneMainAngleNine[0], size, viewWidth, 180f,
+ true, 1f);
+ mCaneMainSmallAngleSix = resizeImage(res, caneMainAngleSix[0], size, viewWidth, 180f,
+ true, 1f);
+ mCaneMainSmallAngleTwelve = resizeImage(res, caneMainAngleTwelve[0], size, viewWidth,
+ 180f, true, 1f);
+ mCaneMainReverseTinyAngleTwelve = resizeImage(res, caneMainTinyReverseAngleSix[0],
+ size, viewWidth, 180f, true, 1f);
+ mCaneMainLargeAngleSix = resizeImage(res, caneMainLargeAngleSix[0], size, viewWidth,
+ 180f, true, 1f);
+ mCaneMainMedAngleSix = resizeImage(res, caneMainMedAngleSix[0], size, viewWidth, 180f,
+ true, 1f);
+
+ mCaneHook = resizeImage(res, caneHook[0], size, viewWidth, 180f, false, 1);
+ mCaneHookFlip = resizeImage(res, caneHook[0], size, viewWidth, 180f, true, 1);
+ mCaneHookReverse = resizeImage(res, caneHookReverse[0], size, viewWidth, 180f, false, 1);
+ mCaneHookReverseFlip = resizeImage(res, caneHookReverse[0], size, viewWidth, 180f, true,
+ 1);
+ mCaneEnd = resizeImage(res, caneEnd[0], size, viewWidth, 180f, false, 1);
+ mCaneEndFlip = resizeImage(res, caneEnd[0], size, viewWidth, 180f, true, 1);
+ mCaneEndReverse = resizeImage(res, caneEndReverse[0], size, viewWidth, 180f, false, 1);
+ mCaneEndReverseFlip = resizeImage(res, caneEndReverse[0], size, viewWidth, 180f, true,
+ 1);
+ mPipeSides = resizeImage(res, pipes[0], size, viewWidth, 180f, true, 1);
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Advance the countdown
+ if (mGameCountDown != null) {
+ mGameCountDown.tick();
+ }
+
+ // Load bitmaps if they haven't been initialised yet
+ if (mGumballBlue == null) {
+ loadBitmaps();
+ }
+ // reset and initialise the canvas for a fresh draw
+ canvas.drawColor(Color.TRANSPARENT);
+ canvas.translate(0, getHeight());
+ canvas.scale(1.0f, -1.0f);
+ float scale = getWidth() / 10.0f;
+ mPaint.setAntiAlias(true);
+ // Iterate through all of the bodies in the game world and draw the corresponding bitmaps
+ Body body = mWorld.getWorld().getBodyList();
+ while (body != null) {
+ if (body.m_userData == null || body.m_userData.equals(PIPE_BOTTOM)) {
+ body = body.getNext();
+ continue;
+ }
+ // Skip bodies with empty fixtures or shapes
+ Fixture fixture = body.getFixtureList();
+ if (fixture == null) {
+ body = body.getNext();
+ continue;
+ }
+ Shape shape = fixture.getShape();
+ if (shape == null) {
+ body = body.getNext();
+ continue;
+ }
+
+ // Get the position.
+ Vec2 position = body.getPosition();
+
+ // Get the bitmap of this body.
+ Bitmap bitmap = null;
+ if (body.getUserData() instanceof Gumball) {
+ // For a gumball, load the correct color
+ Gumball gumball = (Gumball) body.getUserData();
+ if (gumball.mGumballColorId == GUMBALL_BLUE) {
+ bitmap = mGumballBlue;
+ } else if (gumball.mGumballColorId == GUMBALL_YELLOW) {
+ bitmap = mGumballYellow;
+ } else if (gumball.mGumballColorId == GUMBALL_RED) {
+ bitmap = mGumballRed;
+ } else if (gumball.mGumballColorId == GUMBALL_GREEN) {
+ bitmap = mGumballGreen;
+ } else if (gumball.mGumballColorId == GUMBALL_ORANGE) {
+ bitmap = mGumballOrange;
+ } else if (gumball.mGumballColorId == GUMBALL_PURPLE) {
+ bitmap = mGumballPurple;
+ }
+ } else if (body.m_userData.equals(CANE_MAIN_LONG)) {
+ bitmap = mCaneMainLong;
+ } else if (body.m_userData.equals(CANE_MAIN_LONG_REVERSE)) {
+ bitmap = mCaneMainLongReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_MEDIUM)) {
+ bitmap = mCaneMainMed;
+ } else if (body.m_userData.equals(CANE_MAIN_MEDIUM_REVERSE)) {
+ bitmap = mCaneMainMedReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL)) {
+ bitmap = mCaneMainSmall;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_REVERSE)) {
+ bitmap = mCaneMainSmallReverse;
+ } else if (body.m_userData.equals(CANE_MAIN_TINY)) {
+ bitmap = mCaneMainTiny;
+ } else if (body.m_userData.equals(CANE_MAIN_TINY_REVERSE)) {
+ bitmap = mCaneMainTinyReverse;
+ } else if (body.m_userData.equals(CANE_HOOK)) {
+ bitmap = mCaneHook;
+ } else if (body.m_userData.equals(CANE_HOOK_FLIP)) {
+ bitmap = mCaneHookFlip;
+ } else if (body.m_userData.equals(CANE_HOOK_REVERSE)) {
+ bitmap = mCaneHookReverse;
+ } else if (body.m_userData.equals(CANE_HOOK_REVERSE_FLIP)) {
+ bitmap = mCaneHookReverseFlip;
+ } else if (body.m_userData.equals(CANE_END)) {
+ bitmap = mCaneEnd;
+ } else if (body.m_userData.equals(CANE_END_FLIP)) {
+ bitmap = mCaneEndFlip;
+ } else if (body.m_userData.equals(CANE_END_REVERSE)) {
+ bitmap = mCaneEndReverse;
+ } else if (body.m_userData.equals(CANE_END_REVERSE_FLIP)) {
+ bitmap = mCaneEndReverseFlip;
+ } else if (body.m_userData.equals(PIPE_SIDES)) {
+ bitmap = mPipeSides;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_NINE)) {
+ bitmap = mCaneMainSmallAngleNine;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_SIX)) {
+ bitmap = mCaneMainSmallAngleSix;
+ } else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_TWELVE)) {
+ bitmap = mCaneMainSmallAngleTwelve;
+ } else if (body.m_userData.equals(CANE_MAIN_REVERSE_TINY_ANGLE_SIX)) {
+ bitmap = mCaneMainReverseTinyAngleTwelve;
+ } else if (body.m_userData.equals(CANE_MAIN_LARGE_ANGLE_SIX)) {
+ bitmap = mCaneMainLargeAngleSix;
+ } else if (body.m_userData.equals(CANE_MAIN_MED_ANGLE_SIX)) {
+ bitmap = mCaneMainMedAngleSix;
+ }
+
+ if (shape instanceof CircleShape && bitmap != null) {
+ // Draw a gumball
+ CircleShape circleShape = (CircleShape) shape;
+ canvas.save();
+ canvas.rotate((float) (180 * body.getAngle() / Math.PI), scale * position.x,
+ scale * position.y);
+ canvas.drawBitmap(bitmap, scale * (position.x - circleShape.m_radius),
+ scale * (position.y - circleShape.m_radius), mPaint);
+ canvas.restore();
+
+ } else if (shape instanceof EdgeShape && bitmap != null) {
+ // Draw all other objects
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.rotate((float) (180 * body.getAngle() / Math.PI), scale * position.x,
+ scale * position.y);
+ canvas.drawBitmap(bitmap, scale * (position.x), scale * (position.y), mPaint);
+ canvas.restore();
+
+ }
+
+ // Continue drawing with the next body in the world
+ body = body.getNext();
+ }
+ }
+
+ public void setGameCountDown(TiltGameFragment.GameCountdown gameCountDown) {
+ mGameCountDown = gameCountDown;
+ }
+
+ /**
+ * Returns the index of a randomly colored gumball.
+ */
+ public static int getRandomGumballId() {
+ int index = sRandomGenerator.nextInt(GUMBALLS.length);
+ int id = GUMBALLS[index];
+ return id;
+ }
+
+ /**
+ * Gets the correct bitmap based on screen size and rotates and flips the image.
+ */
+ private static Bitmap resizeImage(Resources res, int resourceId, int size, int viewWidth,
+ float rotationDegrees, boolean isFlipped, float caneScale) {
+
+ Matrix matrix = new Matrix();
+ Bitmap bmp = BitmapFactory.decodeResource(res, resourceId);
+
+ if (rotationDegrees != 361f) {
+ matrix.setRotate(rotationDegrees, bmp.getWidth() / 2, bmp.getHeight() / 2);
+ }
+ if (isFlipped) {
+ matrix.preScale(-1, 1);
+ }
+ float scale = ((float) viewWidth) / size;
+ matrix.postScale(scale, scale);
+
+ bmp = Bitmap
+ .createBitmap(bmp, 0, 0, (int) (bmp.getWidth() * caneScale), bmp.getHeight(),
+ matrix, true);
+
+ return bmp;
+ }
+
+ /**
+ * Sets the Box 2D physics world to draw.
+ */
+ public void setModel(PhysicsWorld world) {
+ mWorld = world;
+ }
+
+ /**
+ * Adds a gumball for drawing.
+ */
+ public void addGumball(Gumball gumball) {
+ gumball.mGumballColorId = getRandomGumballId();
+ mWorld.addGumball(gumball.mXInitPos, gumball.mYInitPos, gumball, GUMBALL_DENSITY,
+ GUMBALL_RADIUS, GUMBALL_BOUNCE, GUMBALL_FRICTION, BodyType.DYNAMIC);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Utils.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Utils.java
new file mode 100644
index 000000000..358547453
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/gumball/Utils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 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.games.gumball;
+
+
+import android.app.Activity;
+import android.os.Build;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+
+
+/**
+ * @author kchapman
+ */
+public class Utils {
+
+ private static final String TAG = "SantaTracker";
+
+ /**
+ * Checks if the user has at least API level 9
+ */
+ public static boolean hasGingerbread() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
+ }
+
+ /**
+ * Checks if the user has at least API level 11
+ */
+ public static boolean hasHoneycomb() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ /**
+ * Checks if the user has at least API level 12
+ */
+ public static boolean hasHoneycombMR1() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
+ }
+
+ /**
+ * Checks if the user has at least API level 16
+ */
+ public static boolean hasIceCreamSandwich() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+ }
+
+ /**
+ * Checks if the user has at least API level 16
+ */
+ public static boolean hasJellyBean() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ }
+
+ /**
+ * Checks if the user has at least API level 19
+ */
+ public static boolean hasKitKat() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ }
+
+ public static int getLevelRawFile(int levelNumber) {
+ if (levelNumber > 13) {
+ levelNumber = (levelNumber % 13) + 1;
+ }
+ switch (levelNumber) {
+ case 1:
+ return R.raw.level1;
+ case 2:
+ return R.raw.level2;
+ case 3:
+ return R.raw.level3;
+ case 4:
+ return R.raw.level4;
+ case 5:
+ return R.raw.level5;
+ case 6:
+ return R.raw.level6;
+ case 7:
+ return R.raw.level7;
+ case 8:
+ return R.raw.level8;
+ case 9:
+ return R.raw.level9;
+ case 10:
+ return R.raw.level10;
+ case 11:
+ return R.raw.level11;
+ case 12:
+ return R.raw.level12;
+ case 13:
+ return R.raw.level13;
+ default:
+ return R.raw.level1;
+ }
+ }
+
+ public static PlayGamesActivity getPlayGamesActivity(Fragment fragment) {
+ Activity act = fragment.getActivity();
+ if (act == null || !(act instanceof PlayGamesActivity)) {
+ Log.w(TAG, "Fragment is not in a PlayGamesActivity!");
+ return null;
+ }
+ return (PlayGamesActivity) act;
+ }
+
+ public static boolean isSignedIn(Fragment fragment) {
+ PlayGamesActivity gamesActivity = getPlayGamesActivity(fragment);
+ return gamesActivity != null && gamesActivity.isSignedIn();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackActivity.java
new file mode 100644
index 000000000..cae67f5f0
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 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.games.jetpack;
+
+import android.os.Bundle;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.gamebase.SceneActivity;
+import com.google.android.apps.santatracker.games.simpleengine.Scene;
+import com.google.android.apps.santatracker.games.simpleengine.SceneManager;
+import com.google.android.apps.santatracker.launch.StartupActivity;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+public class JetpackActivity extends SceneActivity {
+
+ private FirebaseAnalytics mMeasurement;
+
+ public JetpackActivity() {
+ super(R.layout.activity_jetpack, StartupActivity.class);
+
+ // [ANALYTICS SCREEN]: Jetpack
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_jetpack);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // App Measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_jetpack));
+ }
+
+ @Override
+ protected Scene getGameScene() {
+ return new JetpackScene();
+ }
+
+ @Override
+ public void onBackPressed() {
+ boolean handled = false;
+ Scene scene = SceneManager.getInstance().getCurrentScene();
+ if (scene instanceof JetpackScene) {
+ handled = ((JetpackScene) scene).onBackKeyPressed();
+ }
+ if (!handled) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public String getGameId() {
+ return getResources().getString(R.string.jetpack_game_id);
+ }
+
+ @Override
+ public String getGameTitle() {
+ return getString(R.string.elf_jetpack);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackConfig.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackConfig.java
new file mode 100644
index 000000000..3580dd31f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackConfig.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 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.games.jetpack;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.gamebase.GameConfig;
+
+public class JetpackConfig {
+
+ // player settings
+ public static class Player {
+
+ public static final float WIDTH = 0.15f;
+ public static final float COLLIDER_WIDTH = 0.15f;
+ public static final float COLLIDER_HEIGHT = 0.20f;
+ public static final float MAX_SPEED = 20.0f;
+
+ // how large is the "no fly zone" near the edges of the screen, where
+ // the player can't go
+ public static final float HORIZ_MOVEMENT_MARGIN = COLLIDER_WIDTH / 2;
+ public static final float VERT_MOVEMENT_MARGIN = COLLIDER_HEIGHT / 2 + 0.05f;
+
+ public static class SpriteAngle {
+
+ // the sprite angle is proportional to how far the player is from the target position
+ public static final float ANGLE_CONST = 5000.0f;
+ // per 1.0 units of distance from target
+ public static final float MAX_ANGLE = 22.0f;
+ public static final float MAX_CHANGE_RATE = 600.0f;
+ public static final int FILTER_SAMPLES = 2;
+ }
+
+ // jetpack fire animation
+ public static class Fire {
+
+ public static final float WIDTH = 0.4f * Player.WIDTH;
+ public static final float ANIM_PERIOD = 0.5f;
+ public static final float ANIM_AMPLITUDE = 0.05f;
+ }
+ }
+
+ // input settings
+ public static class Input {
+
+ public static final float TOUCH_SENSIVITY = 1.2f;
+ public static final float KEY_SENSIVITY = 0.15f;
+ }
+
+ // item settings
+ public static class Items {
+
+ public static final float CANDY_WIDTH = 0.05f;
+ public static final float CANDY_COLLIDER_HEIGHT = CANDY_WIDTH * 2;
+ public static final float CANDY_COLLIDER_WIDTH = 0.1f;
+ public static final float PRESENT_WIDTH = 0.12f;
+ public static final float PRESENT_HEIGHT = PRESENT_WIDTH * 2;
+ public static final float PRESENT_COLLIDER_WIDTH = 0.10f;
+ public static final float PRESENT_COLLIDER_HEIGHT = PRESENT_COLLIDER_WIDTH * 2;
+ public static final float SMALL_WIDTH = 0.05f;
+ public static final float SMALL_HEIGHT = SMALL_WIDTH * 2;
+ public static final float SMALL_COLLIDER_WIDTH = 0.05f;
+ public static final float SMALL_COLLIDER_HEIGHT = SMALL_COLLIDER_WIDTH * 2;
+
+ // item spawn settings
+ public static final float SPAWN_INTERVAL = 1.2f;
+ public static final float SPAWN_Y = 0.8f;
+ public static final float FALL_SPEED_MIN = 0.1f;
+ public static final float FALL_SPEED_MAX = 0.5f;
+ public static final float FALL_SPEED_LEVEL_MULT = 1.2f;
+ public static final float DELETE_Y = -0.8f;
+
+ // what's the initial value for the small items?
+ public static final int BASE_VALUE = 50;
+
+ // index of the "base value" variable of an item
+ public static final int IVAR_BASE_VALUE = 0;
+
+ // index of the "item type" integer variable of an item
+ public static final int IVAR_TYPE = 1;
+
+ // candy rotational speed
+ public static final float CANDY_ROTATE_SPEED = 180.0f;
+
+ // maximum interval between two item collections for them to be
+ // considered a combo
+ public static final float COMBO_INTERVAL = 0.25f;
+ }
+
+ public static class ComboPopup {
+
+ public static final float SIZE = 0.1f;
+ public static final float VEL_Y = GameConfig.ScorePopup.POPUP_VEL_Y * 0.3f;
+ }
+
+ public static final int SKY_COLOR = 0xff91d2f2;
+
+ public static class Clouds {
+
+ public static final int COUNT = 6;
+ public static final float WIDTH = 0.2f;
+ public static final float SPAWN_Y = 0.8f;
+ public static final float SPEED_MIN = 0.3f;
+ public static final float SPEED_MAX = 0.5f;
+ public static final float DELETE_Y = -0.8f;
+ }
+
+ public static class Time {
+
+ // how much time the player has at the beginning
+ public static final float INITIAL = 30.0f;
+
+ // maximum remaining time player can have
+ public static final float MAX = INITIAL * 2;
+
+ // how many seconds are recovered by picking up an item, at the beginning of the game
+ public static final float RECOVERED_BY_ITEM = 2.0f;
+
+ // by how many seconds the time reward decreases per level gained
+ public static final float RECOVERED_DECREASE_PER_LEVEL = 0.5f;
+
+ // the minimum # of seconds recovered by catching a present
+ public static final float RECOVERED_MIN = 1.0f;
+ }
+
+ public static class Progression {
+
+ // how many items must be collected to go up a level?
+ public static final int ITEMS_PER_LEVEL = 10;
+ // by how much the score multiplier increases when we go up a level?
+ public static final float SCORE_LEVEL_MULT = 1.5f;
+ }
+
+ // background music asset file
+ public static final String BGM_ASSET_FILE = "jetpack_music.mp3";
+
+ // Achievements
+ public static class Achievements {
+
+ // combo-based achievements
+ public static final int[] COMBO_ACHS = {
+ R.string.achievement_jetpack_2_combo,
+ R.string.achievement_jetpack_3_combo,
+ R.string.achievement_jetpack_4_combo
+ };
+
+ // score-based
+ public static final int[] SCORE_ACHS = {
+ R.string.achievement_jetpack_beginner_score_500,
+ R.string.achievement_jetpack_intermediate_score_1000,
+ R.string.achievement_jetpack_pro_score_5000,
+ R.string.achievement_jetpack_advanced_score_10000,
+ R.string.achievement_jetpack_expert_score_50000
+ };
+
+ // score necessary for the corresponding SCORE_ACHS achievement
+ public static final int[] SCORE_FOR_ACH = {500, 1000, 5000, 10000, 50000};
+
+ // flight time achievements (one increment = one second)
+ public static final int[] TOTAL_TIME_ACHS = {
+ R.string.achievement_jetpack_flight_time_15,
+ R.string.achievement_jetpack_flight_time_30,
+ R.string.achievement_jetpack_flight_time_60
+ };
+
+ // total presents achievements
+ public static final int[] TOTAL_PRESENTS_ACHS = {
+ R.string.achievement_jetpack_a_dozen_presents,
+ R.string.achievement_jetpack_a_dozen_dozen_presents
+ };
+
+ // total candy achievements
+ public static final int[] TOTAL_CANDY_ACHS = {
+ R.string.achievement_jetpack_candy_for_one_month_30,
+ R.string.achievement_jetpack_candy_for_one_year_365
+ };
+
+ // interval between consecutive sending of incremental achievements
+ public static final float INC_ACH_SEND_INTERVAL = 15.0f;
+ }
+
+ // leaderboard
+ public static final int LEADERBOARD = R.string.leaderboard_jetpack_high_scores;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackObjectFactory.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackObjectFactory.java
new file mode 100644
index 000000000..d4fb9914e
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackObjectFactory.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2015 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.games.jetpack;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.gamebase.GameConfig;
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+import com.google.android.apps.santatracker.games.simpleengine.game.GameObject;
+import com.google.android.apps.santatracker.games.simpleengine.game.World;
+
+import java.util.Random;
+
+public class JetpackObjectFactory {
+
+ Renderer mRenderer;
+ World mWorld;
+ Random mRandom = new Random();
+
+ // item subtypes
+ public static final int ITEM_PRESENT = 0;
+ public static final int ITEM_CANDY = 1;
+ public static final int ITEM_SMALL = 2;
+
+ // Textures
+ int mTexPlayer;
+ int[] mTexItemCandy;
+ int[] mTexItemSmall;
+ int[] mTexItemPresent;
+ int mTexCloud;
+ int[] mComboTex;
+ int mTexFire;
+
+ JetpackObjectFactory(Renderer r, World w) {
+ mRenderer = r;
+ mWorld = w;
+ }
+
+ GameObject makePlayer() {
+ GameObject p = mWorld
+ .newGameObjectWithImage(JetpackScene.TYPE_PLAYER, 0.0f, 0.0f, mTexPlayer,
+ JetpackConfig.Player.WIDTH, Float.NaN);
+ p.setBoxCollider(JetpackConfig.Player.COLLIDER_WIDTH,
+ JetpackConfig.Player.COLLIDER_HEIGHT);
+
+ Renderer.Sprite fireSprite = p.getSprite(p.addSprite());
+ fireSprite.texIndex = mTexFire;
+ fireSprite.width = JetpackConfig.Player.Fire.WIDTH;
+ fireSprite.height = Float.NaN;
+ fireSprite.tintFactor = 0.0f;
+ return p;
+ }
+
+ GameObject makeRandomItem(float fallSpeedMultiplier) {
+ float minX = mRenderer.getLeft() + 2 * JetpackConfig.Items.PRESENT_WIDTH;
+ float maxX = mRenderer.getRight() - 2 * JetpackConfig.Items.PRESENT_WIDTH;
+ float x = minX + mRandom.nextFloat() * (maxX - minX);
+
+ // 0 is candy, 1 is small item, 2 is present
+ int itemType = mRandom.nextInt(3);
+ int itemSubtype = mRandom.nextInt(4); // one of the 4 subtypes
+
+ int tex;
+ float width;
+ float colliderWidth, colliderHeight;
+ boolean isLarge = false;
+
+ switch (itemType) {
+ case ITEM_CANDY:
+ tex = mTexItemCandy[itemSubtype];
+ width = JetpackConfig.Items.CANDY_WIDTH;
+ colliderWidth = JetpackConfig.Items.CANDY_COLLIDER_WIDTH;
+ colliderHeight = JetpackConfig.Items.CANDY_COLLIDER_HEIGHT;
+ break;
+ case ITEM_SMALL:
+ tex = mTexItemSmall[itemSubtype];
+ width = JetpackConfig.Items.SMALL_WIDTH;
+ colliderWidth = JetpackConfig.Items.SMALL_COLLIDER_WIDTH;
+ colliderHeight = JetpackConfig.Items.SMALL_COLLIDER_HEIGHT;
+ break;
+ default:
+ tex = mTexItemPresent[itemSubtype];
+ width = JetpackConfig.Items.PRESENT_WIDTH;
+ colliderWidth = JetpackConfig.Items.PRESENT_COLLIDER_WIDTH;
+ colliderHeight = JetpackConfig.Items.PRESENT_COLLIDER_HEIGHT;
+ isLarge = true;
+ break;
+ }
+
+ GameObject p = mWorld
+ .newGameObjectWithImage(JetpackScene.TYPE_ITEM, x, JetpackConfig.Items.SPAWN_Y,
+ tex, width, Float.NaN);
+
+ p.velY = -(JetpackConfig.Items.FALL_SPEED_MIN + mRandom.nextFloat() *
+ (JetpackConfig.Items.FALL_SPEED_MAX - JetpackConfig.Items.FALL_SPEED_MIN));
+ p.velY *= fallSpeedMultiplier;
+ p.setBoxCollider(colliderWidth, colliderHeight);
+ p.ivar[JetpackConfig.Items.IVAR_BASE_VALUE] = isLarge ? JetpackConfig.Items.BASE_VALUE * 2 :
+ JetpackConfig.Items.BASE_VALUE;
+ p.ivar[JetpackConfig.Items.IVAR_TYPE] = itemType;
+ return p;
+ }
+
+ GameObject makeCloud() {
+ return mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, 0.0f, 0.0f, mTexCloud,
+ JetpackConfig.Clouds.WIDTH, Float.NaN);
+ }
+
+ GameObject makeComboPopup(int comboItems, float x, float y) {
+ int i = comboItems - 2;
+ i = i < 0 ? 0 : i >= mComboTex.length ? mComboTex.length - 1 : i;
+ GameObject o = mWorld.newGameObjectWithImage(GameConfig.TYPE_DECOR, x, y,
+ mComboTex[i], JetpackConfig.ComboPopup.SIZE, Float.NaN);
+ o.velY = JetpackConfig.ComboPopup.VEL_Y;
+ o.timeToLive = GameConfig.ScorePopup.POPUP_EXPIRE;
+ return o;
+ }
+
+ protected void requestTextures() {
+ // request player texture
+ mTexPlayer = mRenderer.requestImageTex(R.drawable.jetpack_player, "jetpack_player",
+ Renderer.DIM_WIDTH, JetpackConfig.Player.WIDTH);
+
+ // request item textures
+ mTexItemCandy = new int[4];
+ int i = 0;
+ for (int resId : new int[]{R.drawable.jetpack_candy1, R.drawable.jetpack_candy2,
+ R.drawable.jetpack_candy3, R.drawable.jetpack_candy4}) {
+ mTexItemCandy[i++] = mRenderer.requestImageTex(resId, "candy", Renderer.DIM_WIDTH,
+ JetpackConfig.Items.CANDY_WIDTH);
+ }
+ mTexItemPresent = new int[4];
+ i = 0;
+ for (int resId : new int[]{R.drawable.jetpack_present1, R.drawable.jetpack_present2,
+ R.drawable.jetpack_present3, R.drawable.jetpack_present4}) {
+ mTexItemPresent[i++] = mRenderer.requestImageTex(resId, "present", Renderer.DIM_WIDTH,
+ JetpackConfig.Items.PRESENT_WIDTH);
+ }
+ mTexItemSmall = new int[4];
+ i = 0;
+ for (int resId : new int[]{R.drawable.jetpack_small1, R.drawable.jetpack_small2,
+ R.drawable.jetpack_small3, R.drawable.jetpack_small4}) {
+ mTexItemSmall[i++] = mRenderer.requestImageTex(resId, "small", Renderer.DIM_WIDTH,
+ JetpackConfig.Items.SMALL_WIDTH);
+ }
+
+ mTexCloud = mRenderer.requestImageTex(R.drawable.jetpack_cloud, "jetpack_cloud",
+ Renderer.DIM_WIDTH, JetpackConfig.Clouds.WIDTH);
+
+ mComboTex = new int[3];
+ mComboTex[0] = mRenderer.requestImageTex(R.drawable.jetpack_combo_2x, "jetpack_combo_2x",
+ Renderer.DIM_WIDTH, JetpackConfig.ComboPopup.SIZE);
+ mComboTex[1] = mRenderer.requestImageTex(R.drawable.jetpack_combo_3x, "jetpack_combo_3x",
+ Renderer.DIM_WIDTH, JetpackConfig.ComboPopup.SIZE);
+ mComboTex[2] = mRenderer.requestImageTex(R.drawable.jetpack_combo_4x, "jetpack_combo_4x",
+ Renderer.DIM_WIDTH, JetpackConfig.ComboPopup.SIZE);
+ mTexFire = mRenderer.requestImageTex(R.drawable.jetpack_fire, "jetpack_fire",
+ Renderer.DIM_WIDTH, JetpackConfig.Player.Fire.WIDTH);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackScene.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackScene.java
new file mode 100644
index 000000000..8ce0f7f70
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/jetpack/JetpackScene.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2015 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.games.jetpack;
+
+import android.view.KeyEvent;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.gamebase.BaseScene;
+import com.google.android.apps.santatracker.games.gamebase.SceneActivity;
+import com.google.android.apps.santatracker.games.simpleengine.Logger;
+import com.google.android.apps.santatracker.games.simpleengine.SceneManager;
+import com.google.android.apps.santatracker.games.simpleengine.SmoothValue;
+import com.google.android.apps.santatracker.games.simpleengine.SoundManager;
+import com.google.android.apps.santatracker.games.simpleengine.game.GameObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public final class JetpackScene extends BaseScene {
+
+ // GameObject types:
+ static final int TYPE_PLAYER = 0;
+ static final int TYPE_ITEM = 1;
+
+ // player
+ GameObject mPlayerObj;
+
+ // our object factory
+ JetpackObjectFactory mFactory;
+
+ // current difficulty level
+ int mLevel = 1;
+
+ // total items collected
+ int mItemsCollected = 0;
+
+ // item fall speed multipler (increases with level)
+ float mFallMult = 1.0f;
+
+ // score multipler (increases with level)
+ float mScoreMult = 1.0f;
+
+ SmoothValue mSpriteAngle = new SmoothValue(0.0f,
+ JetpackConfig.Player.SpriteAngle.MAX_CHANGE_RATE,
+ -JetpackConfig.Player.SpriteAngle.MAX_ANGLE,
+ JetpackConfig.Player.SpriteAngle.MAX_ANGLE,
+ JetpackConfig.Player.SpriteAngle.FILTER_SAMPLES);
+
+ float mPlayerTargetX = 0.0f;
+ float mPlayerTargetY = 0.0f;
+
+ // working array
+ ArrayList mTmpList = new ArrayList();
+
+ // how long til we spawn the next item?
+ float mSpawnCountdown = JetpackConfig.Items.SPAWN_INTERVAL;
+
+ // cloud sprites
+ GameObject[] mCloudObj = new GameObject[JetpackConfig.Clouds.COUNT];
+
+ // time remaining
+ float mTimeRemaining = JetpackConfig.Time.INITIAL;
+
+ // sfx IDs
+ int[] mItemSfx = null;
+
+ // current combo
+ private class Combo {
+
+ int items = 0;
+ float countdown = 0.0f;
+ float centroidX, centroidY;
+ float points;
+ float timeRecovery;
+
+ void reset() {
+ items = 0;
+ countdown = centroidX = centroidY = points = timeRecovery = 0.0f;
+ }
+ }
+
+ private Combo mCombo = new Combo();
+
+ // set of achievements we know we unlocked (to prevent repeated API calls)
+ private HashSet mUnlockedAchievements = new HashSet();
+
+ // achievement increments we are pending to send
+ private int mAchPendingPresents = 0;
+ private int mAchPendingCandy = 0;
+ private float mAchPendingSeconds = 0;
+
+ // countdown to next sending of incremental achievements
+ private float mIncAchCountdown = JetpackConfig.Achievements.INC_ACH_SEND_INTERVAL;
+
+ // what pointer Id is the one that's steering the elf
+ private int mActivePointerId = -1;
+
+ // accumulated play time
+ private float mPlayTime = 0.0f;
+
+ @Override
+ protected String getBgmAssetFile() {
+ return JetpackConfig.BGM_ASSET_FILE;
+ }
+
+ @Override
+ protected float getDisplayedTime() {
+ return mTimeRemaining;
+ }
+
+ @Override
+ protected BaseScene makeNewScene() {
+ return new JetpackScene();
+ }
+
+ @Override
+ public void onInstall() {
+ super.onInstall();
+ mFactory = new JetpackObjectFactory(mRenderer, mWorld);
+ mFactory.requestTextures();
+
+ mPlayerObj = mFactory.makePlayer();
+
+ SoundManager soundManager = SceneManager.getInstance().getSoundManager();
+ mItemSfx = new int[3];
+ mItemSfx[0] = soundManager.requestSfx(R.raw.jetpack_score1);
+ mItemSfx[1] = soundManager.requestSfx(R.raw.jetpack_score2);
+ mItemSfx[2] = soundManager.requestSfx(R.raw.jetpack_score3);
+
+ // start paused
+ pauseGame();
+ }
+
+ @Override
+ public void onUninstall() {
+ super.onUninstall();
+ }
+
+ @Override
+ public void doStandbyFrame(float deltaT) {
+ super.doStandbyFrame(deltaT);
+ mRenderer.setClearColor(JetpackConfig.SKY_COLOR);
+ }
+
+ @Override
+ public void doFrame(float deltaT) {
+ if (mPaused) {
+ deltaT = 0;
+ }
+
+ if (!mGameEnded) {
+ mPlayTime += deltaT;
+ updatePlayer(deltaT);
+ detectCollectedPresents();
+ updateTimeRemaining(deltaT);
+ updateCombo(deltaT);
+ checkLevelUp();
+ mAchPendingSeconds += deltaT;
+ }
+
+ updateClouds();
+ updateCandy(deltaT);
+ killMissedPresents();
+
+ mIncAchCountdown -= deltaT;
+ sendIncrementalAchievements(false);
+
+ if (!mGameEnded && (mSpawnCountdown -= deltaT) < 0.0f) {
+ mSpawnCountdown = JetpackConfig.Items.SPAWN_INTERVAL;
+ mFactory.makeRandomItem(mFallMult);
+ }
+
+ mRenderer.setClearColor(JetpackConfig.SKY_COLOR);
+ super.doFrame(deltaT);
+ }
+
+ protected void endGame() {
+ super.endGame();
+
+ // hide the player
+ mPlayerObj.hide();
+
+ // delete all remaining items
+ killAllPresents();
+
+ // force send all incremental achievements
+ sendIncrementalAchievements(true);
+
+ // submit our score
+ submitScore(JetpackConfig.LEADERBOARD, mScore);
+ }
+
+ private void updateTimeRemaining(float deltaT) {
+ mTimeRemaining -= deltaT;
+ if (mTimeRemaining < 0.0f) {
+ endGame();
+ }
+ }
+
+ private float sineWave(float period, float amplitude, float t) {
+ return (float) Math.sin(2 * Math.PI * t / period) * amplitude;
+ }
+
+ private void updatePlayer(float deltaT) {
+ mSpriteAngle.setTarget(
+ (mPlayerObj.x - mPlayerTargetX) * JetpackConfig.Player.SpriteAngle.ANGLE_CONST);
+ mSpriteAngle.update(deltaT);
+ mPlayerObj.getSprite(0).rotation = mSpriteAngle.getValue();
+ mPlayerObj.getSprite(1).rotation = mSpriteAngle.getValue();
+ mPlayerObj.getSprite(1).width = JetpackConfig.Player.Fire.WIDTH *
+ (1.0f + sineWave(JetpackConfig.Player.Fire.ANIM_PERIOD,
+ JetpackConfig.Player.Fire.ANIM_AMPLITUDE, mPlayTime));
+ mPlayerObj.getSprite(1).height = Float.NaN; // proportional to width
+
+ if (isTv()) {
+ // On TV, player moves based on its speed.
+ mPlayerObj.displaceBy(mPlayerObj.velX * deltaT, mPlayerObj.velY * deltaT);
+ } else {
+ mPlayerObj.displaceTowards(mPlayerTargetX, mPlayerTargetY, deltaT *
+ JetpackConfig.Player.MAX_SPEED);
+ }
+ }
+
+ private void updateClouds() {
+ int i;
+ for (i = 0; i < mCloudObj.length; i++) {
+ GameObject o = mCloudObj[i];
+ if (o == null) {
+ o = mFactory.makeCloud();
+ mCloudObj[i] = o;
+ setupNewCloud(o);
+ } else if (o.y < JetpackConfig.Clouds.DELETE_Y) {
+ setupNewCloud(o);
+ }
+ }
+ }
+
+ private void updateCombo(float deltaT) {
+ if (mCombo.items > 0 && (mCombo.countdown -= deltaT) <= 0.0f) {
+ endCombo();
+ }
+ }
+
+ private boolean isCandy(GameObject o) {
+ return o.type == TYPE_ITEM &&
+ o.ivar[JetpackConfig.Items.IVAR_TYPE] == JetpackObjectFactory.ITEM_CANDY;
+ }
+
+ private boolean isPresent(GameObject o) {
+ return o.type == TYPE_ITEM &&
+ o.ivar[JetpackConfig.Items.IVAR_TYPE] == JetpackObjectFactory.ITEM_PRESENT;
+ }
+
+ private void updateCandy(float deltaT) {
+ int i;
+ for (i = 0; i < mWorld.gameObjects.size(); i++) {
+ GameObject o = mWorld.gameObjects.get(i);
+ if (isCandy(o)) {
+ o.getSprite(0).rotation += deltaT * JetpackConfig.Items.CANDY_ROTATE_SPEED;
+ }
+ }
+ }
+
+ private void setupNewCloud(GameObject o) {
+ o.displaceTo(mRenderer.getLeft() + mRandom.nextFloat() * (mRenderer.getRight() -
+ mRenderer.getLeft()), JetpackConfig.Clouds.SPAWN_Y);
+ o.velY = -(JetpackConfig.Clouds.SPEED_MIN + mRandom.nextFloat() *
+ (JetpackConfig.Clouds.SPEED_MAX - JetpackConfig.Clouds.SPEED_MIN));
+ }
+
+ private void killMissedPresents() {
+ int i;
+ for (i = 0; i < mWorld.gameObjects.size(); i++) {
+ GameObject o = mWorld.gameObjects.get(i);
+ if (o.type == TYPE_ITEM && o.y < JetpackConfig.Items.DELETE_Y) {
+ o.dead = true;
+ }
+ }
+ }
+
+ private void killAllPresents() {
+ int i;
+ for (i = 0; i < mWorld.gameObjects.size(); i++) {
+ GameObject o = mWorld.gameObjects.get(i);
+ if (o.type == TYPE_ITEM) {
+ o.dead = true;
+ }
+ }
+ }
+
+ private int roundScore(int score) {
+ score = (score / 50) * 50;
+ return score < 50 ? 50 : score;
+ }
+
+ private void addScore(float score) {
+ mScore += score;
+ unlockScoreBasedAchievements();
+ }
+
+ private void addTime(float time) {
+ mTimeRemaining += time;
+ if (mTimeRemaining > JetpackConfig.Time.MAX) {
+ mTimeRemaining = JetpackConfig.Time.MAX;
+ }
+ }
+
+ private void pickUpItem(GameObject item) {
+ int baseValue = item.ivar[JetpackConfig.Items.IVAR_BASE_VALUE];
+ int value = roundScore((int) (baseValue * mScoreMult));
+
+ if (isCandy(item)) {
+ mAchPendingCandy++;
+ } else if (isPresent(item)) {
+ mAchPendingPresents++;
+ }
+
+ mObjectFactory.makeScorePopup(item.x, item.y, value, mDigitFactory);
+ float thisX = item.x;
+ float thisY = item.y;
+ item.dead = true;
+ mItemsCollected++;
+
+ float timeRecovery = JetpackConfig.Time.RECOVERED_BY_ITEM -
+ mLevel * JetpackConfig.Time.RECOVERED_DECREASE_PER_LEVEL;
+ if (timeRecovery < JetpackConfig.Time.RECOVERED_MIN) {
+ timeRecovery = JetpackConfig.Time.RECOVERED_MIN;
+ }
+
+ // rewards: score and time
+ addScore(value);
+ addTime(timeRecovery);
+
+ // play sfx
+ SceneManager.getInstance().getSoundManager().playSfx(
+ mItemSfx[mRandom.nextInt(mItemSfx.length)]);
+
+ // increment combo
+ mCombo.centroidX = (mCombo.centroidX * mCombo.items + thisX) / (mCombo.items + 1);
+ mCombo.centroidY = (mCombo.centroidY * mCombo.items + thisY) / (mCombo.items + 1);
+ mCombo.items++;
+ mCombo.countdown = JetpackConfig.Items.COMBO_INTERVAL;
+ mCombo.points += value;
+ mCombo.timeRecovery += timeRecovery;
+ }
+
+ private void detectCollectedPresents() {
+ mWorld.detectCollisions(mPlayerObj, mTmpList, true);
+ int i;
+ for (i = 0; i < mTmpList.size(); i++) {
+ GameObject o = mTmpList.get(i);
+ if (o.type == TYPE_ITEM) {
+ pickUpItem(o);
+ }
+ }
+ }
+
+ private void endCombo() {
+ if (mCombo.items > 1) {
+ mFactory.makeComboPopup(mCombo.items, mCombo.centroidX, mCombo.centroidY);
+
+ // give bonus
+ addScore(mCombo.points * mCombo.items);
+ addTime(mCombo.timeRecovery * mCombo.items);
+
+ // unlock combo-based achievements
+ unlockComboBasedAchievements(mCombo.items);
+ }
+ mCombo.reset();
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, float x, float y) {
+ super.onPointerDown(pointerId, x, y);
+ if (mActivePointerId < 0) {
+ mActivePointerId = pointerId;
+ }
+ }
+
+ @Override
+ public void onPointerUp(int pointerId, float x, float y) {
+ super.onPointerUp(pointerId, x, y);
+ if (mActivePointerId == pointerId) {
+ mActivePointerId = -1;
+ }
+ }
+
+ @Override
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ super.onPointerMove(pointerId, x, y, deltaX, deltaY);
+
+ // if paused, do nothing.
+ if (mPaused) {
+ return;
+ }
+
+ // if no finger owns the steering of the elf, adopt this one.
+ if (mActivePointerId < 0) {
+ mActivePointerId = pointerId;
+ }
+
+ // if this finger is the owner of the steering, steer!
+ if (mActivePointerId == pointerId) {
+ mPlayerTargetX += deltaX * JetpackConfig.Input.TOUCH_SENSIVITY;
+ mPlayerTargetY += deltaY * JetpackConfig.Input.TOUCH_SENSIVITY;
+
+ // don't let the player wander off screen
+ limitPlayerMovement();
+ }
+ }
+
+ @Override
+ public void onKeyDown(int keyCode, int repeatCount) {
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_BUTTON_A:
+ onConfirmKeyPressed();
+ break;
+ case KeyEvent.KEYCODE_BUTTON_B:
+ onBackKeyPressed();
+ break;
+ }
+
+ if (!mPaused) {
+ float absVelocity = JetpackConfig.Player.WIDTH
+ * (1.5f + (float) repeatCount * JetpackConfig.Input.KEY_SENSIVITY);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ mPlayerTargetY = Integer.MAX_VALUE;
+ mPlayerObj.velY = absVelocity;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ mPlayerTargetY = Integer.MIN_VALUE;
+ mPlayerObj.velY = -absVelocity;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ mPlayerTargetX = Integer.MIN_VALUE;
+ mPlayerObj.velX = -absVelocity;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ mPlayerTargetX = Integer.MAX_VALUE;
+ mPlayerObj.velX = absVelocity;
+ break;
+ }
+ // don't let the player wander off screen
+ limitPlayerMovement();
+ }
+ }
+
+ @Override
+ public void onKeyUp(int keyCode) {
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ // if it's going up, stop it.
+ mPlayerTargetY = mPlayerObj.y;
+ if (mPlayerObj.velY > 0) {
+ mPlayerObj.velY = 0;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ // if it's going down, stop it.
+ mPlayerTargetY = mPlayerObj.y;
+ if (mPlayerObj.velY < 0) {
+ mPlayerObj.velY = 0;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ // if it's going left, stop it.
+ mPlayerTargetX = mPlayerObj.x;
+ if (mPlayerObj.velX < 0) {
+ mPlayerObj.velX = 0;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ // if it's going right, stop it.
+ mPlayerTargetX = mPlayerObj.x;
+ if (mPlayerObj.velX > 0) {
+ mPlayerObj.velX = 0;
+ }
+ break;
+ }
+
+ // don't let the player wander off screen
+ limitPlayerMovement();
+ }
+
+ private void limitPlayerMovement() {
+ float minX = mRenderer.getLeft() + JetpackConfig.Player.HORIZ_MOVEMENT_MARGIN;
+ float maxX = mRenderer.getRight() - JetpackConfig.Player.HORIZ_MOVEMENT_MARGIN;
+ float minY = mRenderer.getBottom() + JetpackConfig.Player.VERT_MOVEMENT_MARGIN;
+ float maxY = mRenderer.getTop() - JetpackConfig.Player.VERT_MOVEMENT_MARGIN;
+
+ if (isTv()) {
+ mPlayerObj.velX = mPlayerObj.x + mPlayerObj.velX < minX ? minX - mPlayerObj.x :
+ mPlayerObj.x + mPlayerObj.velX > maxX ? maxX - mPlayerObj.x : mPlayerObj.velX;
+
+ mPlayerObj.velY = mPlayerObj.y + mPlayerObj.velY < minY ? minY - mPlayerObj.y :
+ mPlayerObj.y + mPlayerObj.velY > maxY ? maxY - mPlayerObj.y : mPlayerObj.velY;
+ } else {
+ mPlayerTargetX = mPlayerTargetX < minX ? minX :
+ mPlayerTargetX > maxX ? maxX : mPlayerTargetX;
+ mPlayerTargetY = mPlayerTargetY < minY ? minY :
+ mPlayerTargetY > maxY ? maxY : mPlayerTargetY;
+ }
+ }
+
+
+ private void checkLevelUp() {
+ int dueLevel = mItemsCollected / JetpackConfig.Progression.ITEMS_PER_LEVEL;
+ while (mLevel < dueLevel) {
+ mLevel++;
+ Logger.d("Level up! Now at level " + mLevel);
+ mFallMult *= JetpackConfig.Items.FALL_SPEED_LEVEL_MULT;
+ mScoreMult *= JetpackConfig.Progression.SCORE_LEVEL_MULT;
+ }
+ }
+
+ private void unlockScoreBasedAchievements() {
+ int i;
+ for (i = 0; i < JetpackConfig.Achievements.SCORE_ACHS.length; i++) {
+ if (mScore >= JetpackConfig.Achievements.SCORE_FOR_ACH[i]) {
+ unlockAchievement(JetpackConfig.Achievements.SCORE_ACHS[i]);
+ }
+ }
+ }
+
+ private void unlockComboBasedAchievements(int comboSize) {
+ int i;
+ for (i = 0; i < JetpackConfig.Achievements.COMBO_ACHS.length; i++) {
+ // COMBO_ACHS[n] is the achievement to unlock for a combo of size n + 2
+ if (comboSize >= i + 2) {
+ unlockAchievement(JetpackConfig.Achievements.COMBO_ACHS[i]);
+ }
+ }
+ }
+
+ private void sendIncrementalAchievements(boolean force) {
+ if (!force && mIncAchCountdown > 0.0f) {
+ // it's not time to send yet
+ return;
+ }
+ if (SceneManager.getInstance().getActivity() == null) {
+ // no Activity (maybe we're in the background), so can't send yet
+ return;
+ }
+
+ if (mAchPendingCandy > 0) {
+ incrementAchievements(JetpackConfig.Achievements.TOTAL_CANDY_ACHS, mAchPendingCandy);
+ mAchPendingCandy = 0;
+ }
+ if (mAchPendingPresents > 0) {
+ incrementAchievements(JetpackConfig.Achievements.TOTAL_PRESENTS_ACHS,
+ mAchPendingPresents);
+ mAchPendingPresents = 0;
+ }
+ if (mAchPendingSeconds >= 1.0f) {
+ int seconds = (int) Math.floor(mAchPendingSeconds);
+ incrementAchievements(JetpackConfig.Achievements.TOTAL_TIME_ACHS, seconds);
+ mAchPendingSeconds -= seconds;
+ }
+
+ // submit score as well, since we're at it.
+ submitScore(JetpackConfig.LEADERBOARD, mScore);
+
+ // reset countdown
+ mIncAchCountdown = JetpackConfig.Achievements.INC_ACH_SEND_INTERVAL;
+ }
+
+ private void unlockAchievement(int resId) {
+ SceneActivity act = (SceneActivity) SceneManager.getInstance().getActivity();
+ if (!mUnlockedAchievements.contains(resId) && act != null) {
+ act.postUnlockAchievement(resId);
+ mUnlockedAchievements.add(resId);
+ }
+ }
+
+ private void incrementAchievements(int[] resId, int steps) {
+ for (int i : resId) {
+ incrementAchievement(i, steps);
+ }
+ }
+
+ private void incrementAchievement(int resId, int steps) {
+ SceneActivity act = (SceneActivity) SceneManager.getInstance().getActivity();
+ if (steps > 0 && act != null) {
+ act.postIncrementAchievement(resId, steps);
+ }
+ }
+
+ private void submitScore(int resId, int score) {
+ SceneActivity act = (SceneActivity) SceneManager.getInstance().getActivity();
+ if (act != null) {
+ act.postSubmitScore(resId, score);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/CircleView.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/CircleView.java
new file mode 100644
index 000000000..caf58bfec
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/CircleView.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.games.matching;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * View that contains a round red circle, with a cut out in its center.
+ * Used for the end level screen.
+ */
+public class CircleView extends View {
+
+ private Paint mPaint = new Paint();
+ private Path mInsideCircle = new Path();
+
+ public CircleView(Context context) {
+ super(context);
+ }
+
+ public CircleView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mPaint.setColor(Color.RED);
+ mInsideCircle.addCircle(getWidth() / 2, getHeight() / 2, getHeight() / 512, Direction.CW);
+ mInsideCircle.close();
+ try {
+ canvas.clipPath(mInsideCircle, Region.Op.XOR);
+ } catch (UnsupportedOperationException e) {
+ //ignore clipping path for devices that don't support it (i.e. ICS)
+ }
+
+ mPaint.setColor(Color.RED);
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, getHeight() / 8, mPaint);
+
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/LevelTextView.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/LevelTextView.java
new file mode 100644
index 000000000..1edb33ee8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/LevelTextView.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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.games.matching;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * View that displays the level at 60% of the available height of the canvas.
+ */
+public class LevelTextView extends View {
+
+ private Paint mPaint = new Paint();
+ private int mLevelNumber = 0;
+ private Typeface mRobotoTypeface;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public LevelTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mRobotoTypeface = Typeface.createFromAsset(getContext().getAssets(),
+ "RobotoCondensed-Regular.ttf");
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setTextAlign(Align.CENTER);
+ // Text size is set as 60% of the available height of the canvas
+ mPaint.setTextSize(getHeight() / 6);
+ mPaint.setTypeface(mRobotoTypeface);
+ canvas.drawText(String.valueOf(mLevelNumber), getWidth() / 2,
+ (getHeight() - (mPaint.ascent() + mPaint.descent())) / 2, mPaint);
+ }
+
+ public void setLevelNumber(int levelNumber) {
+ mLevelNumber = levelNumber;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MatchingGameConstants.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MatchingGameConstants.java
new file mode 100644
index 000000000..85b11665b
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MatchingGameConstants.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.games.matching;
+
+import com.google.android.apps.santatracker.R;
+
+/**
+ * Constants for the memory match and gumball games.
+ */
+public class MatchingGameConstants {
+
+ /**
+ * Name of the preferences file for the gumball and memory match games.
+ */
+ public static final String PREFERENCES_FILENAME = "match_gumball_games";
+ /**
+ * Key of the preference indicating that the memory match instructions have been viewed.
+ */
+ public static final String MATCH_INSTRUCTIONS_VIEWED = "MATCH_INSTRUCTIONS_VIEWED";
+ /**
+ * Key of the preference indicating that the gumball instructions have been viewed.
+ */
+ public static final String GUMBALL_INSTRUCTIONS_VIEWED = "GUMBALL_INSTRUCTIONS_VIEWED";
+
+ /**
+ * ID of the string resource pointing to the Play Games leaderboard game ID for the memory match
+ * game.
+ */
+ public static final int LEADERBOARDS_MATCH = R.string.leaderboard_memory;
+ /**
+ * ID of the string resource pointing to the Play Games leaderboard game ID for the gumball
+ * game.
+ */
+ public static final int LEADERBOARDS_GUMBALL = R.string.leaderboard_gumball;
+
+ /**
+ * Initial time for the gumball game.
+ */
+ public static final long GUMBALL_INIT_TIME = 60000;
+ /**
+ * Time to add to the countdown when a gumball is dropped.
+ */
+ public static final long GUMBALL_ADDED_TIME = 5000;
+ /**
+ * Initial time for the memory match game.
+ */
+ public static final long MATCH_INIT_TIME = 60000;
+ /**
+ * Time to add to the countdown for each successful match in the memory match game.
+ */
+ public static final long MATCH_ADD_TIME_NEXT_LEVEL = 5000;
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryCard.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryCard.java
new file mode 100644
index 000000000..e22d4a6aa
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryCard.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 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.games.matching;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Card in the memory game.
+ * Contains the front of the card (the card image) and its back (its cloak).
+ */
+public class MemoryCard {
+
+ public int mCardImageId;
+ public int mCardCloakId;
+ public View mView;
+
+ public MemoryCard(int cardImageId, int cardCloakId) {
+ mCardImageId = cardImageId;
+ mCardCloakId = cardCloakId;
+ }
+
+ /**
+ * Generate a randomised list of {@link com.google.android.apps.santatracker.games.matching.MemoryCard}s.
+ *
+ * @param numCards Number of cards to generate
+ * @param cardImages List of card image references
+ * @param cardCloaks List of card cloak image references
+ */
+ public static ArrayList getGameCards(int numCards, List cardImages,
+ List cardCloaks) {
+ Collections.shuffle(cardImages);
+ Collections.shuffle(cardCloaks);
+ ArrayList cards = new ArrayList();
+ for (int i = 0; i < (numCards / 2); i++) {
+ cards.add(new MemoryCard(cardImages.get(i), cardCloaks.get(i)));
+ cards.add(new MemoryCard(cardImages.get(i), cardCloaks.get(i)));
+ }
+ Collections.shuffle(cards);
+ return cards;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryMatchFragment.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryMatchFragment.java
new file mode 100644
index 000000000..8f06cf980
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/matching/MemoryMatchFragment.java
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2015 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.games.matching;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.Callback;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
+import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
+import com.google.android.apps.santatracker.games.gumball.Utils;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Fragment that contains the memory match game.
+ */
+public class MemoryMatchFragment extends Fragment
+ implements OnClickListener, AnimationListener, Callback {
+
+ /**
+ * Drawables for all card faces.
+ */
+ private static final Integer[] CARD_FACE_DRAWABLES = new Integer[]{
+ R.drawable.mmg_card_ball, R.drawable.mmg_card_balloon, R.drawable.mmg_card_beachball,
+ R.drawable.mmg_card_candle, R.drawable.mmg_card_globe, R.drawable.mmg_card_gumball,
+ R.drawable.mmg_card_penguin, R.drawable.mmg_card_rabbit, R.drawable.mmg_card_reindeer,
+ R.drawable.mmg_card_snowman, R.drawable.mmg_card_tree, R.drawable.mmg_card_trophy};
+ /**
+ * Drawables for all card face cloaks (background color).
+ */
+ private static final Integer[] CARD_CLOAK_DRAWABLES = new Integer[]{
+ R.drawable.mmg_card_cloak_blue_dark, R.drawable.mmg_card_cloak_blue_light,
+ R.drawable.mmg_card_cloak_orange, R.drawable.mmg_card_cloak_purple,
+ R.drawable.mmg_card_cloak_red, R.drawable.mmg_card_cloak_orange};
+
+ /**
+ * Current game level.
+ */
+ private int mLevelNumber = 1;
+
+ /**
+ * Number of correct moves required for this level.
+ */
+ private int mCorrectMovesRequired = 0;
+
+ /**
+ * Count of correct moves in this level so far.
+ */
+ private int mCurrentCorrectMoves = 0;
+
+ /**
+ * Total score of the game so far.
+ */
+ private int mMatchScore = 0;
+
+ /**
+ * Count of the number of wrong selections in the level so far.
+ */
+ private int mWrongAnswers = 0;
+
+ /**
+ * First card that has been selected and is visible.
+ */
+ private View mVisibleCard1 = null;
+
+ /**
+ * Second card that has been selected and is visible.
+ */
+ private View mVisibleCard2 = null;
+
+ /**
+ * First card that was visible and is being animated to become hidden again.
+ */
+ private View mHiddenCard1;
+
+ /**
+ * Second card that was visible and is being animated to become hidden again.
+ */
+ private View mHiddenCard2;
+
+ /**
+ * Views that represent the cards (doors) on screen.
+ */
+ private View[] mViewCard = new View[12];
+
+ /**
+ * List of card faces.
+ * This list is shuffled before each level.
+ */
+ private List mCardFaceIds = Arrays.asList(CARD_FACE_DRAWABLES);
+
+ /**
+ * List of card cloaks (backgrounds).
+ * This list is shuffled before each level.
+ */
+ private List mCardCloakIds = Arrays.asList(CARD_CLOAK_DRAWABLES);
+
+ /**
+ * Time left in the game in milliseconds.
+ */
+ private long mTimeLeftInMillis = MatchingGameConstants.MATCH_INIT_TIME;
+ /**
+ * Countdown timer refresh interval in milliseconds.
+ */
+ private long mCountDownInterval = 1000;
+ /**
+ * Countdown timer that drives the game logic.
+ */
+ private GameCountdown mCountDownTimer = null;
+ /**
+ * Flag to indicate the state of this Fragment and stop the game correctly when the countdown
+ * expires.
+ */
+ private boolean wasPaused = false;
+
+ private Animation mAnimationRightPaneSlideOut;
+ private Animation mAnimationLeftPaneSlideOut;
+ private Animation mAnimationLeftPaneSlideIn;
+ private Animation mAnimationRightPaneSlideIn;
+ private Animation mAnimationScaleLevelDown;
+ private Animation mAnimationLevelFadeOut;
+ private Animation mAnimationLevelScaleUp;
+ private Animation mAnimationPlayAgainBackground;
+ private Animation mAnimationPlayAgainMain;
+ private Animation mAnimationCardCover;
+ private TranslateAnimation mAnimationSnowman;
+ private Animation mAnimationTimerAlpha;
+ private TranslateAnimation mAnimationSnowmanBack;
+ private AnimationSet mAnimationSetSnowman;
+
+ private CircleView mEndLevelCircle;
+ private TextView mScoreText;
+ private LevelTextView mLevelNumberText;
+
+ private int mSoundDoorOpen = -1;
+ private int mSoundDoorClose = -1;
+ private int mSoundMatchWrong = -1;
+ private int mSoundMatchRight = -1;
+ private int mSoundBeep = -1;
+ private int mSoundGameOver = -1;
+ private MediaPlayer mBackgroundMusic;
+ private SoundPool mSoundPool;
+
+ private TextView mTimerTextView;
+ private View mViewPlayAgainBackground;
+ private View mViewPlayAgainMain;
+ private TextView mTextPlayAgainScore;
+ private TextView mTextPlayAgainLevel;
+ private ImageView mButtonPlay;
+ private ImageView mButtonPause;
+ private ImageButton mButtonBigPlay;
+ private Button mPlayAgainBtn;
+ private ImageButton mButtonCancelBar;
+ private ImageButton mButtonMenu;
+ private ImageView mInviteButton;
+ private ImageView mViewInstructions;
+ private View mViewPauseOverlay;
+ private View mViewBonusSnowman;
+ private AnimationDrawable mInstructionDrawable;
+ private ImageView mViewGPlusSignIn;
+ private View mLayoutGPlus;
+
+ /**
+ * Handler that dismisses the game instructions when the game is started for the first time.
+ */
+ private Handler mDelayHandler = new Handler();
+
+ /**
+ * Preferences that store whether the game instructions have been viewed.
+ */
+ private SharedPreferences mPreferences;
+
+ /**
+ * Indicates whether the screen is clickable.
+ * It is disabled when a full screen animation is in progress at the end of the level or at the
+ * end of the game.
+ */
+ private boolean isClickable = true;
+
+ private AppInvitesFragment mInvitesFragment;
+
+ /**
+ * Create a new instance of this fragment.
+ */
+ public static MemoryMatchFragment newInstance() {
+ return new MemoryMatchFragment();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_memory_match, container, false);
+ rootView.setKeepScreenOn(true);
+
+ // Below ICS, display a special, simplified background for the entire fragment
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ rootView.findViewById(R.id.match_score_layout).setBackgroundResource(
+ R.drawable.score_background_gingerbread);
+ }
+
+ // Initialise the sound pool and all sound effects
+ mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
+ mSoundDoorOpen = mSoundPool.load(getActivity(), R.raw.mmg_open_door_3, 1);
+ mSoundDoorClose = mSoundPool.load(getActivity(), R.raw.mmg_close_door, 1);
+ mSoundMatchWrong = mSoundPool.load(getActivity(), R.raw.mmg_wrong, 1);
+ mSoundMatchRight = mSoundPool.load(getActivity(), R.raw.mmg_right, 1);
+ mSoundGameOver = mSoundPool.load(getActivity(), R.raw.gameover, 1);
+ mSoundBeep = mSoundPool.load(getActivity(), R.raw.mmg_open_door_2, 1);
+
+ // Set up all animations.
+ loadAnimations();
+
+ // G+ sign-in views
+ mViewGPlusSignIn = (ImageView) rootView.findViewById(R.id.gplus_button);
+ mViewGPlusSignIn.setOnClickListener(this);
+ mLayoutGPlus = rootView.findViewById(R.id.play_again_gplus);
+ mLayoutGPlus.setVisibility(View.GONE);
+
+ // 'Play again' screen views
+ mTextPlayAgainScore = (TextView) rootView.findViewById(R.id.play_again_score);
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainLevel = (TextView) rootView.findViewById(R.id.play_again_level);
+ mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));
+ mViewPlayAgainBackground = rootView.findViewById(R.id.play_again_bkgrd);
+ mViewPlayAgainMain = rootView.findViewById(R.id.play_again_main);
+ mPlayAgainBtn = (Button) rootView.findViewById(R.id.play_again_btn);
+ mPlayAgainBtn.setOnClickListener(this);
+
+ // Level, countdown and score views at the bottom of the screen
+ mTimerTextView = (TextView) rootView.findViewById(R.id.match_timer);
+ mLevelNumberText = (LevelTextView) rootView.findViewById(R.id.card_end_level_number);
+ mLevelNumberText.setVisibility(View.GONE);
+ mScoreText = (TextView) rootView.findViewById(R.id.match_score);
+ mScoreText.setText(String.valueOf(mMatchScore));
+
+ // End of level animated circle
+ mEndLevelCircle = (CircleView) rootView.findViewById(R.id.card_end_level_circle);
+ mEndLevelCircle.setVisibility(View.GONE);
+
+ // The snowman that is animated as a bonus when the player is particularly awesome
+ mViewBonusSnowman = rootView.findViewById(R.id.match_snowman);
+
+ // 'Pause' screen views
+ mButtonMenu = (ImageButton) rootView.findViewById(R.id.main_menu_button);
+ mButtonMenu.setOnClickListener(this);
+ mButtonMenu.setVisibility(View.GONE);
+ mInviteButton = (ImageView) rootView.findViewById(R.id.invite_button);
+ mInviteButton.setOnClickListener(this);
+ mInviteButton.setVisibility(View.GONE);
+ mButtonPlay = (ImageView) rootView.findViewById(R.id.match_play_button);
+ mButtonPlay.setOnClickListener(this);
+ mButtonPlay.setVisibility(View.GONE);
+ mButtonPause = (ImageView) rootView.findViewById(R.id.match_pause_button);
+ mButtonPause.setOnClickListener(this);
+ mButtonPause.setVisibility(View.VISIBLE);
+ mViewPauseOverlay = rootView.findViewById(R.id.match_pause_overlay);
+ mViewPauseOverlay.setVisibility(View.GONE);
+ mButtonBigPlay = (ImageButton) rootView.findViewById(R.id.match_big_play_button);
+ mButtonBigPlay.setOnClickListener(this);
+ mButtonCancelBar = (ImageButton) rootView.findViewById(R.id.match_cancel_bar);
+ mButtonCancelBar.setOnClickListener(this);
+ mButtonCancelBar.setVisibility(View.GONE);
+
+ // Playing cards (doors)
+ mViewCard[0] = rootView.findViewById(R.id.card_position_1);
+ mViewCard[1] = rootView.findViewById(R.id.card_position_2);
+ mViewCard[2] = rootView.findViewById(R.id.card_position_3);
+ mViewCard[3] = rootView.findViewById(R.id.card_position_4);
+ mViewCard[4] = rootView.findViewById(R.id.card_position_5);
+ mViewCard[5] = rootView.findViewById(R.id.card_position_6);
+ mViewCard[6] = rootView.findViewById(R.id.card_position_7);
+ mViewCard[7] = rootView.findViewById(R.id.card_position_8);
+ mViewCard[8] = rootView.findViewById(R.id.card_position_9);
+ mViewCard[9] = rootView.findViewById(R.id.card_position_10);
+ mViewCard[10] = rootView.findViewById(R.id.card_position_11);
+ mViewCard[11] = rootView.findViewById(R.id.card_position_12);
+
+
+
+ // Display the instructions if they haven't been seen by the player yet.
+ mPreferences = getActivity()
+ .getSharedPreferences(MatchingGameConstants.PREFERENCES_FILENAME,
+ Context.MODE_PRIVATE);
+ if (!mPreferences.getBoolean(MatchingGameConstants.MATCH_INSTRUCTIONS_VIEWED, false)) {
+ // Instructions haven't been viewed yet. Construct an AnimationDrawable with instructions.
+ mInstructionDrawable = new AnimationDrawable();
+ mInstructionDrawable
+ .addFrame(getResources().getDrawable(R.drawable.instructions_touch_1), 300);
+ mInstructionDrawable
+ .addFrame(getResources().getDrawable(R.drawable.instructions_touch_2), 300);
+ mInstructionDrawable.setOneShot(false);
+ mViewInstructions = (ImageView) rootView.findViewById(R.id.instructions);
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ mViewInstructions.setImageResource(R.drawable.instructions_touch_1);
+ } else {
+ mViewInstructions.setImageDrawable(mInstructionDrawable);
+ mInstructionDrawable.start();
+ }
+ // Set a timer to hide the instructions after 2 seconds
+ mViewInstructions.postDelayed(new StartGameDelay(), 2000);
+ } else {
+ //Instructions have already been viewed. Start the first level.
+ setUpLevel();
+ }
+
+ return rootView;
+ }
+
+ /**
+ * Load and initialise all animations required for the game.
+ */
+ private void loadAnimations(){
+ mAnimationTimerAlpha = new AlphaAnimation(0.0f, 1.0f);
+ mAnimationTimerAlpha.setDuration(1000);
+ mAnimationTimerAlpha.setRepeatMode(Animation.REVERSE);
+ mAnimationTimerAlpha.setRepeatCount(Animation.INFINITE);
+
+ mAnimationPlayAgainBackground = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.play_again_bkgrd_anim);
+ mAnimationPlayAgainBackground.setFillAfter(true);
+ mAnimationPlayAgainBackground.setAnimationListener(this);
+ mAnimationCardCover = AnimationUtils.loadAnimation(getActivity(), R.anim.card_answer_flash);
+ mAnimationCardCover.setFillAfter(true);
+ mAnimationPlayAgainMain = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.play_again_main_anim);
+ mAnimationPlayAgainMain.setFillAfter(true);
+ mAnimationPlayAgainMain.setAnimationListener(this);
+ // Special bonus animation to play if the player is particularly awesome.
+ mAnimationSetSnowman = new AnimationSet(true);
+ mAnimationSnowman = new TranslateAnimation(150, 0, 150, 0);
+ mAnimationSnowman.setDuration(1000);
+ mAnimationSetSnowman.addAnimation(mAnimationSnowman);
+ mAnimationSnowmanBack = new TranslateAnimation(0, 150, 0, 150);
+ mAnimationSnowmanBack.setDuration(1000);
+ mAnimationSnowmanBack.setStartOffset(1500);
+ mAnimationSnowmanBack.setAnimationListener(this);
+ mAnimationSetSnowman.addAnimation(mAnimationSnowmanBack);
+ mAnimationSetSnowman.setAnimationListener(this);
+
+ mAnimationRightPaneSlideOut = AnimationUtils
+ .loadAnimation(getActivity(), android.R.anim.slide_out_right);
+ mAnimationRightPaneSlideOut.setFillAfter(true);
+ mAnimationLeftPaneSlideOut = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.left_pane_slide_out);
+ mAnimationLeftPaneSlideOut.setFillAfter(true);
+ mAnimationLeftPaneSlideIn = AnimationUtils
+ .loadAnimation(getActivity(), android.R.anim.slide_in_left);
+ mAnimationLeftPaneSlideIn.setFillAfter(true);
+ mAnimationRightPaneSlideIn = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.right_pane_slide_in);
+ mAnimationRightPaneSlideIn.setFillAfter(true);
+ mAnimationScaleLevelDown = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.scale_level_anim_down);
+ mAnimationScaleLevelDown.setAnimationListener(this);
+ mAnimationLevelFadeOut = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.level_fade_out_anim);
+ mAnimationLevelFadeOut.setAnimationListener(this);
+ mAnimationLevelScaleUp = AnimationUtils
+ .loadAnimation(getActivity(), R.anim.scale_up_level_anim);
+ mAnimationLevelScaleUp.setAnimationListener(this);
+ mAnimationRightPaneSlideOut.setAnimationListener(this);
+ mAnimationLeftPaneSlideOut.setAnimationListener(this);
+ }
+
+ /**
+ * Runnable that stars the game after the instructions have been viewed.
+ * It hides the instructions, marks them as viewed and starts the game.
+ */
+ private class StartGameDelay implements Runnable {
+
+ @Override
+ public void run() {
+ // Start the first level.
+ setUpLevel();
+
+ // Hide the instructions.
+ mInstructionDrawable.stop();
+ mViewInstructions.setVisibility(View.GONE);
+ // Mark the instructions as 'viewed'.
+ Editor edit = mPreferences.edit();
+ edit.putBoolean(MatchingGameConstants.MATCH_INSTRUCTIONS_VIEWED, true);
+ edit.commit();
+ }
+
+ }
+
+ public void onSignInSucceeded() {
+ setSignInButtonVisibility(false);
+ }
+
+ public void onSignInFailed() {
+
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mInvitesFragment = AppInvitesFragment.getInstance(getActivity());
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ isClickable = true;
+ if (wasPaused && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+ mCountDownTimer.start();
+ wasPaused = false;
+ }
+ loadBackgroundMusic();
+ updateSignInButtonVisibility();
+ }
+
+ /**
+ * Toggles visibility of the G+ sign in layout if the user is not already signed in.
+ */
+ private void setSignInButtonVisibility(boolean show) {
+ mLayoutGPlus.setVisibility(show && !Utils.isSignedIn(this) ? View.VISIBLE : View.GONE);
+ }
+
+ private void updateSignInButtonVisibility() {
+ if (mLayoutGPlus.getVisibility() == View.VISIBLE && Utils.isSignedIn(this)) {
+ setSignInButtonVisibility(false);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseGame();
+ stopBackgroundMusic();
+ if (mCountDownTimer != null && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
+ mCountDownTimer.cancel();
+ wasPaused = true;
+ }
+ }
+
+ private void stopBackgroundMusic() {
+ if (mBackgroundMusic != null) {
+ mBackgroundMusic.stop();
+ mBackgroundMusic.release();
+ mBackgroundMusic = null;
+ }
+ }
+
+
+ private void loadBackgroundMusic() {
+ mBackgroundMusic = MediaPlayer.create(getActivity(), R.raw.santatracker_musicloop);
+ mBackgroundMusic.setLooping(true);
+ mBackgroundMusic.setVolume(.1f, .1f);
+ mBackgroundMusic.start();
+ }
+
+ /**
+ * Starts the next level.
+ * Shuffles the cards, sets up the views and starts the countdown for the next level.
+ */
+ private void setUpLevel() {
+ mCurrentCorrectMoves = 0;
+ mWrongAnswers = 0;
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ // Display the level number
+ mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));
+
+ // Lock all doors, unlock them individually per level later
+ for (View card : mViewCard) {
+ setUpLockedCard(card);
+ }
+
+ if(mLevelNumber > 1){
+ // Add the 'next level' bonus time
+ mTimeLeftInMillis += MatchingGameConstants.MATCH_ADD_TIME_NEXT_LEVEL;
+ }
+
+ int pairsRequired = Math.min(mLevelNumber, 5) + 1;
+ mCorrectMovesRequired = pairsRequired;
+ ArrayList memoryCards =
+ MemoryCard.getGameCards(pairsRequired * 2, mCardFaceIds, mCardCloakIds);
+ int[] cardSlots;
+
+ if (mLevelNumber == 1) {
+ cardSlots = new int[] {2, 3, 8, 9};
+ } else if (mLevelNumber == 2) {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5};
+ } else if (mLevelNumber == 3) {
+ cardSlots = new int[] {1, 2, 3, 4, 7, 8, 9, 10};
+ } else if (mLevelNumber == 4) {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5, 7, 8, 9, 10};
+ } else {
+ cardSlots = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ }
+ for (int i = 0; i < cardSlots.length; i++) {
+ setUpMemoryCard(mViewCard[cardSlots[i]], memoryCards.get(i));
+ }
+
+ // Start the countdown for the new level
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+ mCountDownTimer.start();
+ }
+
+ /**
+ * Sets the card displayed by this view to the 'locked' state.
+ */
+ private void setUpLockedCard(View view) {
+ view.setOnClickListener(null);
+ view.findViewById(R.id.card_locked).setVisibility(View.VISIBLE);
+ view.findViewById(R.id.card_cloak).setVisibility(View.GONE);
+ view.findViewById(R.id.card_frame).setVisibility(View.GONE);
+ view.findViewById(R.id.card_image).setVisibility(View.GONE);
+ view.findViewById(R.id.card_pane_right).clearAnimation();
+ view.findViewById(R.id.card_pane_left).clearAnimation();
+ view.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ view.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ view.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ }
+
+ /**
+ * Sets the viewCard that displays a card to show the face and cloaking indicated by the
+ * {@link com.google.android.apps.santatracker.games.matching.MemoryCard}.
+ */
+ private void setUpMemoryCard(View viewCard, MemoryCard card) {
+ viewCard.setOnClickListener(this);
+ card.mView = viewCard;
+ viewCard.setTag(card);
+ viewCard.findViewById(R.id.card_locked).setVisibility(View.GONE);
+ viewCard.findViewById(R.id.card_frame).setVisibility(View.VISIBLE);
+ ((ImageView) viewCard.findViewById(R.id.card_cloak)).setImageResource(card.mCardCloakId);
+ viewCard.findViewById(R.id.card_cloak).setVisibility(View.VISIBLE);
+ ((ImageView) viewCard.findViewById(R.id.card_image)).setImageResource(card.mCardImageId);
+ viewCard.findViewById(R.id.card_image).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_pane_right).clearAnimation();
+ viewCard.findViewById(R.id.card_pane_left).clearAnimation();
+ viewCard.findViewById(R.id.card_pane_left).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_pane_right).setVisibility(View.VISIBLE);
+ viewCard.findViewById(R.id.card_cover).setVisibility(View.INVISIBLE);
+ }
+
+ /**
+ * Plays a sound unveils the given card.
+ */
+ private void showCard(View view) {
+ mSoundPool.play(mSoundDoorOpen, 1, 1, 0, 0, 1.0f);
+ view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideOut);
+
+ view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideOut);
+ }
+
+ /**
+ * Plays a sound and hides the given card.
+ */
+ private void hideCard(View view) {
+ mSoundPool.play(mSoundDoorClose, 1, 1, 0, 0, 1.0f);
+ view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideIn);
+ view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideIn);
+ }
+
+ @Override
+ public void onClick(View view) {
+ // Check if the cards are not currently clickable and skip if necessary.
+ if (view.getTag() != null && isClickable) {
+ // A card has been clicked.
+ onCardClick(view);
+ } else if (view.equals(mPlayAgainBtn)) {
+ // The 'play again' button has been clicked. Stop the music and restart.
+ stopBackgroundMusic();
+ loadBackgroundMusic();
+
+ // Reset the game state.
+ resetGameState();
+
+ // Reset the UI and clear all animations.
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.clearAnimation();
+ mViewPlayAgainMain.clearAnimation();
+ mViewPlayAgainBackground.setVisibility(View.GONE);
+ mViewPlayAgainMain.setVisibility(View.GONE);
+ mButtonMenu.setVisibility(View.GONE);
+ mInviteButton.setVisibility(View.GONE);
+ setSignInButtonVisibility(false);
+ } else if (view.equals(mButtonPause)) {
+ // Pause button.
+ pauseGame();
+ } else if (view.equals(mButtonPlay) || view.equals(mButtonBigPlay)) {
+ // Play button, resume the game.
+ resumeGame();
+ } else if (view.equals(mButtonCancelBar) || view.equals(mButtonMenu)) {
+ // Exit the game.
+ exit();
+ } else if (view.equals(mViewGPlusSignIn)) {
+ // Start sign-in flow.
+ PlayGamesActivity activity = Utils.getPlayGamesActivity(this);
+ if (activity != null) {
+ activity.startSignIn();
+ }
+ } else if (view.equals(mInviteButton)) {
+ // Send app invite
+ mInvitesFragment.sendGameInvite(
+ getString(R.string.memory),
+ getString(R.string.memory_game_id),
+ mMatchScore);
+ }
+ }
+
+ private void resetGameState() {
+ mLevelNumber = 1;
+ mTimeLeftInMillis = MatchingGameConstants.MATCH_INIT_TIME;
+ setUpLevel();
+ mMatchScore = 0;
+ }
+
+ /**
+ * Handles onClick events for views that represent cards.
+ * Unveils the card and checks for a match if another card has already been unveiled.
+ */
+ private void onCardClick(View view) {
+ MemoryCard card1 = (MemoryCard) view.getTag();
+ if (mVisibleCard1 != null && mVisibleCard2 != null) {
+ // Two cards are already unveiled, hide them both
+ hideCard(mVisibleCard1);
+ hideCard(mVisibleCard2);
+ mVisibleCard2.setOnClickListener(this);
+ mVisibleCard1.setOnClickListener(this);
+ mVisibleCard1 = view;
+ mVisibleCard2 = null;
+ mVisibleCard1.setOnClickListener(null);
+ showCard(mVisibleCard1);
+ } else if (mVisibleCard1 != null && mVisibleCard2 == null) {
+ // One card is already unveiled and a second one has been selected
+ MemoryCard card2 = (MemoryCard) mVisibleCard1.getTag();
+
+ if (card1.mCardImageId == card2.mCardImageId) {
+ // The second card matches the face of the first one - we have a winner!
+ mVisibleCard2 = view;
+ mVisibleCard2.setOnClickListener(null);
+ mVisibleCard1.setOnClickListener(null);
+ showCard(view);
+ if (mVisibleCard1.findViewById(R.id.card_cover).getVisibility() != View.GONE) {
+ // Play an animation to flash the background of both cards in green
+ mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
+ mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
+ mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mAnimationCardCover.setAnimationListener(this);
+ mHiddenCard1 = mVisibleCard1;
+ mHiddenCard2 = mVisibleCard2;
+ mVisibleCard1.findViewById(R.id.card_cover).startAnimation(
+ mAnimationCardCover);
+ mVisibleCard2.findViewById(R.id.card_cover).startAnimation(
+ mAnimationCardCover);
+
+ mVisibleCard2 = null;
+ mVisibleCard1 = null;
+
+ // Add the cards to the tally of correct cards
+ mCurrentCorrectMoves++;
+ increaseScoreMatch();
+
+ // Check if this level is finished
+ checkNextLevel();
+ }
+ } else {
+ // The second card does not match the first one - this is not a match.
+ mSoundPool.play(mSoundMatchWrong, 1, 1, 0, 0, 1.0f);
+ mWrongAnswers++;
+ mVisibleCard2 = view;
+ mVisibleCard2.setOnClickListener(null);
+ showCard(mVisibleCard2);
+ // Play an animation to flash the background of both cards in red
+ mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
+ mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
+ mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
+ mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
+ mAnimationCardCover.setAnimationListener(null);
+ mVisibleCard1.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+ mVisibleCard2.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
+ }
+ } else {
+ // This is the first card that has been unveiled.
+ mVisibleCard1 = view;
+ mVisibleCard1.setOnClickListener(null);
+ showCard(mVisibleCard1);
+ }
+ }
+
+ /**
+ * Advances the game to the next level if all matching pairs have been unveiled.
+ */
+ private void checkNextLevel() {
+ if (mCurrentCorrectMoves >= mCorrectMovesRequired) {
+ // Increment the level count
+ increaseScoreLevel();
+ mLevelNumber++;
+ mLevelNumberText.setLevelNumber(mLevelNumber);
+ // Start the 'next level' animation after a short delay.
+ mDelayHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mLevelNumberText.startAnimation(mAnimationLevelScaleUp);
+ mEndLevelCircle.startAnimation(mAnimationScaleLevelDown);
+ setUpLevel();
+ }
+ }, 750);
+ }
+ }
+
+ private void exit() {
+ getActivity().finish();
+ }
+
+ private void resumeGame() {
+ mButtonPause.setVisibility(View.VISIBLE);
+ mButtonPlay.setVisibility(View.GONE);
+ mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
+ mCountDownTimer.start();
+ mViewPauseOverlay.setVisibility(View.GONE);
+ mButtonCancelBar.setVisibility(View.GONE);
+ if (Utils.hasKitKat()) {
+ ImmersiveModeHelper.setImmersiveSticky(getActivity().getWindow());
+ }
+ }
+
+ /**
+ * CountDownTimer that handles the game timing logic of the memory match game.
+ */
+ public class GameCountdown extends CountDownTimer {
+
+ private Boolean animationStarted = false;
+
+ public GameCountdown(long millisInFuture, long countDownInterval) {
+ super(millisInFuture, countDownInterval);
+ }
+
+ @Override
+ public void onFinish() {
+ // When the countdown is over, end the game.
+ mTimerTextView.clearAnimation();
+ animationStarted = false;
+ mTimerTextView.setTextColor(Color.WHITE);
+ mTimerTextView.setTypeface(Typeface.DEFAULT);
+ if (mViewPlayAgainBackground.getVisibility() != View.VISIBLE && !wasPaused) {
+ submitScore(MatchingGameConstants.LEADERBOARDS_MATCH, mMatchScore);
+ stopBackgroundMusic();
+
+ // Show the 'play again' screen.
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ mViewPlayAgainBackground.startAnimation(mAnimationPlayAgainBackground);
+ mViewPlayAgainMain.startAnimation(mAnimationPlayAgainMain);
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ mButtonMenu.setVisibility(View.VISIBLE);
+ mInviteButton.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+
+ mSoundPool.play(mSoundGameOver, .2f, .2f, 0, 0, 1.0f);
+ }
+ }
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished);
+ if (seconds >= 6) {
+ animationStarted = false;
+ mTimerTextView.clearAnimation();
+ mTimerTextView.setTypeface(Typeface.DEFAULT);
+ mTimerTextView.setTextColor(Color.WHITE);
+ } else if (!animationStarted) {
+ // Start flashing the countdown time
+ animationStarted = true;
+ mTimerTextView.setTypeface(Typeface.DEFAULT_BOLD);
+ mTimerTextView.setTextColor(Color.RED);
+ mTimerTextView.clearAnimation();
+ mTimerTextView.startAnimation(mAnimationTimerAlpha);
+ }
+
+ // Update the displayed countdown time
+ mTimeLeftInMillis = millisUntilFinished;
+ mTimerTextView.setText(
+ String.format("%d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished),
+ seconds - TimeUnit.MINUTES.toSeconds(
+ TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished))));
+
+ }
+
+ }
+
+ /**
+ * Increases the current score when a match is found.
+ * The score is based on the current level.
+ */
+ private void increaseScoreMatch() {
+ mSoundPool.play(mSoundMatchRight, 1, 1, 0, 0, 1.0f);
+ mMatchScore += (50 * (Math.pow(1.1, mLevelNumber - 1)));
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ }
+
+ /**
+ * Increases the current score when advancing to the next level.
+ */
+ private void increaseScoreLevel() {
+ mMatchScore += (500 * (Math.pow(1.1, mLevelNumber - 1)));
+ if (mLevelNumber > 2 && mWrongAnswers == 0) {
+ // Show an amazing bonus animation if this the player is particularly skillful ;)
+ mViewBonusSnowman.startAnimation(mAnimationSnowman);
+ }
+ mScoreText.setText(String.valueOf(mMatchScore));
+ mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // The game is clickable again now that the animation has ended
+ isClickable = true;
+
+ if (animation == mAnimationScaleLevelDown) {
+ // After the scale level down animation, fade out the end level circle
+ mLevelNumberText.startAnimation(mAnimationLevelFadeOut);
+ mEndLevelCircle.startAnimation(mAnimationLevelFadeOut);
+ } else if (animation == mAnimationLevelFadeOut) {
+ // Hide the end level circle after the animation has finished
+ mEndLevelCircle.clearAnimation();
+ mLevelNumberText.clearAnimation();
+ mLevelNumberText.setVisibility(View.GONE);
+ mEndLevelCircle.setVisibility(View.GONE);
+ } else if (animation == mAnimationSetSnowman) {
+ mViewBonusSnowman.clearAnimation();
+ mViewBonusSnowman.setVisibility(View.GONE);
+ } else if (animation == mAnimationCardCover) {
+ // Reset the state and animations of both cards after they have been hidden again
+ mHiddenCard1.clearAnimation();
+ mHiddenCard2.clearAnimation();
+
+ mHiddenCard1.findViewById(R.id.card_pane_right).clearAnimation();
+ mHiddenCard1.findViewById(R.id.card_pane_left).clearAnimation();
+ mHiddenCard2.findViewById(R.id.card_pane_right).clearAnimation();
+ mHiddenCard2.findViewById(R.id.card_pane_left).clearAnimation();
+ mHiddenCard1.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ mHiddenCard1.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
+ mHiddenCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
+ mHiddenCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
+ mHiddenCard1.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ mHiddenCard2.findViewById(R.id.card_cover).setVisibility(View.GONE);
+ }
+ }
+
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Mark the game as not clickable when an animation is in progress
+ isClickable = false;
+
+ // Mark the correct views as visible before the start of an animation.
+ if (animation == mAnimationScaleLevelDown) {
+ mEndLevelCircle.setVisibility(View.VISIBLE);
+ mLevelNumberText.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationPlayAgainBackground) {
+ mViewPlayAgainBackground.setVisibility(View.VISIBLE);
+ } else if (animation == mAnimationPlayAgainMain) {
+ mViewPlayAgainMain.setVisibility(View.VISIBLE);
+ setSignInButtonVisibility(true);
+ } else if (animation == mAnimationSetSnowman) {
+ mViewBonusSnowman.setVisibility(View.VISIBLE);
+ mViewBonusSnowman.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ mSoundPool.play(mSoundBeep, .5f, .5f, 0, 0, 1.0f);
+ }
+ }, 800);
+ }
+ }
+
+
+ @Override
+ public void invalidateDrawable(Drawable who) {
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ }
+
+ /**
+ * Pauses the game when the back key is pressed.
+ */
+ public void onBackKeyPressed() {
+ if (mViewPlayAgainMain.getVisibility() == View.VISIBLE) {
+ exit();
+ } else {
+ if (mButtonPause.getVisibility() != View.GONE) {// check if already handled
+ pauseGame();
+ } else {
+ resumeGame();
+ }
+ }
+ }
+
+ private void pauseGame() {
+ mButtonPause.setVisibility(View.GONE);
+ mButtonPlay.setVisibility(View.VISIBLE);
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ mViewPauseOverlay.setVisibility(View.VISIBLE);
+ mButtonCancelBar.setVisibility(View.VISIBLE);
+ if (Utils.hasKitKat()) {
+ ImmersiveModeHelper.setImmersiveStickyWithActionBar(getActivity().getWindow());
+ }
+ }
+
+ /**
+ * Submit score to Play Games services and the leader board.
+ */
+ private void submitScore(int resId, int score) {
+ PlayGamesActivity act = Utils.getPlayGamesActivity(this);
+ if (act != null) {
+ act.postSubmitScore(resId, score);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/BitmapTextureMaker.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/BitmapTextureMaker.java
new file mode 100644
index 000000000..d28867032
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/BitmapTextureMaker.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+class BitmapTextureMaker implements Runnable {
+
+ private boolean mStartedLoading = false;
+ private boolean mFinishedLoading = false;
+ static final int DIM_WIDTH = 0;
+ static final int DIM_HEIGHT = 1;
+ private Context mContext = null;
+ private int mScreenWidth = 0;
+ private int mScreenHeight = 0;
+
+ private ArrayList mEntries = new ArrayList();
+
+ BitmapTextureMaker() {
+ }
+
+ public void request(int tag, int resId, String name, int dimType, float maxDim) {
+ if (mStartedLoading) {
+ Logger.e("Can't request a new bitmap after loading has started.");
+ return;
+ }
+
+ BitmapEntry e = new BitmapEntry();
+ e.tag = tag;
+ e.resId = resId;
+ e.dimType = dimType;
+ e.maxDim = maxDim;
+ e.name = name;
+ mEntries.add(e);
+ Logger.d("Bitmap requested: " + e.toString() + ", #" + (mEntries.size() - 1));
+ }
+
+ public void startLoading(Context ctx) {
+ mStartedLoading = true;
+ mContext = ctx.getApplicationContext();
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mScreenWidth = wm.getDefaultDisplay().getWidth();
+ mScreenHeight = wm.getDefaultDisplay().getHeight();
+ Logger.d("Starting async load of bitmaps. Screen dimensions " + mScreenWidth + "screenX" +
+ mScreenHeight);
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ class BitmapEntry {
+
+ int tag;
+ int dimType;
+ float maxDim;
+ int resId;
+ Bitmap bitmap = null;
+ String name = "";
+
+ @Override
+ public String toString() {
+ return "[BitmapEntry name=" + name + ", " +
+ ((dimType == DIM_HEIGHT) ? "maxH=" : "maxW=") + maxDim +
+ ", resId=" + resId + ", bitmap=" + (bitmap == null ? "(null)" : "loaded!" +
+ "]");
+ }
+ }
+
+ @Override
+ public void run() {
+ for (BitmapEntry e : mEntries) {
+ loadBitmapEntry(e);
+ }
+ mContext = null;
+ mFinishedLoading = true;
+ Logger.d("Finished loading bitmaps.");
+ }
+
+ void loadBitmapEntry(BitmapEntry e) {
+ Logger.d("Loading bitmap entry " + e.toString());
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(mContext.getResources(), e.resId, options);
+ int imageHeight = options.outHeight;
+ int imageWidth = options.outWidth;
+ float imageAspect = imageWidth / (float) imageHeight;
+ float screenUnit = (float) mScreenHeight;
+
+ Logger.d(e.name + " dimensions " + imageWidth + "screenX" + imageHeight);
+
+ int reqWidth = 0, reqHeight = 0;
+
+ if (e.dimType == DIM_HEIGHT) {
+ reqHeight = (int) (e.maxDim * screenUnit);
+ reqWidth = (int) (imageAspect * reqHeight);
+ } else if (e.dimType == DIM_WIDTH) {
+ reqWidth = (int) (e.maxDim * screenUnit);
+ reqHeight = (int) (reqWidth / imageAspect);
+ }
+
+ Logger.d(e.name + " requested dimensions " + reqWidth + "screenX" + reqHeight);
+
+ int inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ Logger.d(e.name + " in sample size " + inSampleSize);
+
+ options.inSampleSize = inSampleSize;
+ options.inJustDecodeBounds = false;
+ e.bitmap = BitmapFactory.decodeResource(mContext.getResources(), e.resId, options);
+ Logger.d("Loaded bitmap for " + e.name + ", " + e.bitmap.getWidth() + "x" +
+ e.bitmap.getHeight());
+ }
+
+ // From http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
+ private static int calculateInSampleSize(
+ BitmapFactory.Options options, int reqWidth, int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+
+ public boolean isFinishedLoading() {
+ return mFinishedLoading;
+ }
+
+ public boolean hasStartedLoading() {
+ return mStartedLoading;
+ }
+
+ public Bitmap getBitmap(int index) {
+ if (!mFinishedLoading) {
+ Logger.e("Can't call getBitmap before BitmapTextureMaker is finished loading.");
+ return null;
+ }
+ if (index < 0 || index >= mEntries.size()) {
+ return null;
+ }
+ return mEntries.get(index).bitmap;
+ }
+
+ public int getBitmapCount() {
+ return mEntries.size();
+ }
+
+ public int getTag(int index) {
+ return (index >= 0 && index < mEntries.size()) ? mEntries.get(index).tag : 0;
+ }
+
+ public void dispose() {
+ mFinishedLoading = mStartedLoading = false;
+ for (BitmapEntry e : mEntries) {
+ if (e.bitmap != null) {
+ e.bitmap.recycle();
+ e.bitmap = null;
+ }
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/GameView.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/GameView.java
new file mode 100644
index 000000000..d8af99a19
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/GameView.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+public class GameView extends GLSurfaceView implements GLSurfaceView.Renderer {
+
+ int mSurfWidth = 0;
+ int mSurfHeight = 0;
+
+ public GameView(Context ctx) {
+ super(ctx);
+ setEGLContextClientVersion(2);
+ setRenderer(this);
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
+ SceneManager.getInstance().onGLSurfaceCreated(getContext().getApplicationContext());
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl10, int width, int height) {
+ mSurfWidth = width;
+ mSurfHeight = height;
+ SceneManager.getInstance().onGLSurfaceChanged(width, height);
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl10) {
+ SceneManager.getInstance().onDrawFrame();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ return SceneManager.getInstance().onTouchEvent(e);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return SceneManager.getInstance().onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return SceneManager.getInstance().onKeyUp(keyCode, event);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Logger.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Logger.java
new file mode 100644
index 000000000..60248d1e0
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Logger.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.util.Log;
+
+public class Logger {
+
+ private static final String TAG = "seng";
+ private static boolean debugEnabled = false;
+
+ public static void enableDebugLog(boolean enable) {
+ debugEnabled = enable;
+ if (debugEnabled) {
+ d("Debug logs enabled.");
+ }
+ }
+
+ public static void d(String msg) {
+ if (debugEnabled) {
+ Log.d(TAG, msg);
+ }
+ }
+
+ public static void w(String msg) {
+ Log.w(TAG, "!!! WARNING: " + msg);
+ }
+
+ public static void e(String msg) {
+ Log.e(TAG, "*** ERROR: " + msg);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Renderer.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Renderer.java
new file mode 100644
index 000000000..434bb13d1
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Renderer.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+public final class Renderer {
+
+ public static final int DIM_HEIGHT = BitmapTextureMaker.DIM_HEIGHT;
+ public static final int DIM_WIDTH = BitmapTextureMaker.DIM_WIDTH;
+
+ // for getRelativePos()
+ public static final int REL_CENTER = 0;
+ public static final int REL_LEFT = 1;
+ public static final int REL_TOP = 2;
+ public static final int REL_RIGHT = 3;
+ public static final int REL_BOTTOM = 4;
+
+ private boolean mInitDone = false;
+
+ private ArrayList mSprites = new ArrayList(64);
+ private ArrayList mSpriteRecycleBin = new ArrayList(64);
+
+ // Non null only when we are currently loading bitmaps. So this will be null
+ // if and only if we have all the requested bitmaps ready as textures.
+ private BitmapTextureMaker mBitmapTextureMaker = null;
+
+ // Non null only when we are currently making text textures.
+ private TextTextureMaker mTextTextureMaker = null;
+
+ // current surface dimensions
+ int mSurfWidth = 0;
+ int mSurfHeight = 0;
+
+ // coordinate system bounds
+ RectF mBounds = new RectF();
+
+ // information about each texture requested
+ private ArrayList mTexInfo = new ArrayList();
+
+ private class TexInfo {
+
+ int glTex = 0; // OpenGL texture handle, if loaded; 0 if not yet loaded
+ float aspect; // aspect ratio (width/height), computed when texture is loaded
+ int width, height; // computed when texture is loaded
+
+ // texture request parameters
+ static final int TYPE_IMAGE = 0;
+ static final int TYPE_TEXT = 1;
+
+ int type = TYPE_IMAGE;
+
+ int resId; // (for TYPE_IMAGE, it's a drawable; for TYPE_TEXT, it's a string res id)
+ String name;
+
+ // for TYPE_IMAGE only:
+ int dimType;
+ float maxDim;
+
+ // for TYPE_TEXT only:
+ float fontSize;
+ int textAnchor = TEXT_ANCHOR_CENTER | TEXT_ANCHOR_MIDDLE;
+ int textColor = 0xffffffff;
+ }
+
+ public static final int TEXT_ANCHOR_CENTER = 0x00;
+ public static final int TEXT_ANCHOR_LEFT = 0x01;
+ public static final int TEXT_ANCHOR_RIGHT = 0x02;
+ private static final int TEXT_ANCHOR_HORIZ_MASK = 0x0f;
+ public static final int TEXT_ANCHOR_TOP = 0x10;
+ public static final int TEXT_ANCHOR_BOTTOM = 0x20;
+ public static final int TEXT_ANCHOR_MIDDLE = 0x00;
+ private static final int TEXT_ANCHOR_VERT_MASK = 0xf0;
+
+ // locations of attributes and uniforms in our shader
+ private int mLocMatrix = -1;
+ private int mLocColor = -1;
+ private int mLocTintFactor = -1;
+ private int mLocSampler = -1;
+ private int mLocPosition = -1;
+ private int mLocTexCoord = -1;
+
+ // quad data
+ private static float[] QUAD_GEOM = { // screenX, screenY, z, u, v
+ -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
+ 0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
+ -0.5f, 0.5f, 0.0f, 0.0f, 0.0f,
+ 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
+
+ };
+ private final int SIZEOF_FLOAT = 4;
+ private final int QUAD_GEOM_VERTEX_COUNT = 4;
+ private final int QUAD_GEOM_STRIDE = 5 * SIZEOF_FLOAT;
+ private final int QUAD_GEOM_POS_OFFSET = 0;
+ private final int QUAD_GEOM_TEXCOORD_OFFSET = 3;
+ FloatBuffer mQuadGeomBuf = null;
+
+ // projection matrix
+ float[] mProjMat = null;
+
+ // temp working matrices
+ float[] mTmpMatA = new float[16];
+ float[] mTmpMatB = new float[16];
+
+ // color used to clear the screen
+ private static final int DEFAULT_CLEAR_COLOR = 0xffff0000;
+ int mClearColor = DEFAULT_CLEAR_COLOR;
+
+ public void setClearColor(int color) {
+ mClearColor = color;
+ }
+
+ Renderer() {
+ }
+
+ public void onGLSurfaceCreated(Context ctx) {
+ initGL();
+ refreshTextures(ctx);
+ mInitDone = true;
+ }
+
+ private int compileShader(int type, String source) {
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ return shader;
+ }
+
+ private int linkProgram(int vertShader, int fragShader) {
+ int program = GLES20.glCreateProgram();
+ GLES20.glAttachShader(program, vertShader);
+ GLES20.glAttachShader(program, fragShader);
+ GLES20.glLinkProgram(program);
+ return program;
+ }
+
+ private void initGL() {
+ Logger.d("Initializing OpenGL");
+ GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+ Logger.d("Compiling vertex shader.");
+ int vertShader = compileShader(GLES20.GL_VERTEX_SHADER, ShaderSource.VERTEX_SHADER);
+ Logger.d("Vertex shader is " + vertShader);
+ Logger.d("Vertex shader compilation log: " + GLES20.glGetShaderInfoLog(vertShader));
+ int fragShader = compileShader(GLES20.GL_FRAGMENT_SHADER, ShaderSource.FRAG_SHADER);
+ Logger.d("Fragment shader is " + fragShader);
+ Logger.d("Fragment shader compilation log: " + GLES20.glGetShaderInfoLog(fragShader));
+ int program = linkProgram(vertShader, fragShader);
+ Logger.d("Program is " + program);
+ Logger.d("Program linking log: " + GLES20.glGetProgramInfoLog(program));
+
+ Logger.d("Activating shader.");
+ GLES20.glUseProgram(program);
+
+ // get locations
+ mLocMatrix = GLES20.glGetUniformLocation(program, "u_Matrix");
+ mLocColor = GLES20.glGetUniformLocation(program, "u_Color");
+ mLocTintFactor = GLES20.glGetUniformLocation(program, "u_TintFactor");
+ mLocSampler = GLES20.glGetUniformLocation(program, "u_Sampler");
+ mLocPosition = GLES20.glGetAttribLocation(program, "a_Position");
+ mLocTexCoord = GLES20.glGetAttribLocation(program, "a_TexCoord");
+ Logger.d("Locations: " +
+ "mLocMatrix=" + mLocMatrix + "; " +
+ "mLocColor=" + mLocColor + "; " +
+ "mLocTintFactor=" + mLocTintFactor + "; " +
+ "mLocSampler=" + mLocSampler + "; " +
+ "mLocPosition=" + mLocPosition + "; " +
+ "mLocTexCoord=" + mLocTexCoord);
+
+ ByteBuffer bb = ByteBuffer.allocateDirect(SIZEOF_FLOAT * QUAD_GEOM.length);
+ bb.order(ByteOrder.nativeOrder());
+ mQuadGeomBuf = bb.asFloatBuffer();
+ mQuadGeomBuf.put(QUAD_GEOM);
+ mQuadGeomBuf.position(0);
+
+ // set up opengl blending
+ GLES20.glEnable(GLES20.GL_BLEND);
+
+ // we use GL_ONE instead of GL_SRC_ALPHA because Android premultiplies
+ // the alpha channel in the PNG by r,g,b, so if we use GL_SRC_ALPHA, we get
+ // a gray halo around things.
+ GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ private void pushTex(int tex) {
+ GLES20.glUniform1i(mLocSampler, 0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE0, tex);
+ }
+
+ private void pushColor(float r, float g, float b, float a, float factor) {
+ GLES20.glUniform4f(mLocColor, r, g, b, a);
+ GLES20.glUniform1f(mLocTintFactor, factor);
+ }
+
+ private void drawQuad(float centerX, float centerY, float width, float height,
+ float rotation) {
+ // compute final matrix
+ float[] modelViewM = mTmpMatA;
+ float[] finalM = mTmpMatB;
+ Matrix.setIdentityM(modelViewM, 0);
+ Matrix.translateM(modelViewM, 0, centerX, centerY, 0.0f);
+ Matrix.rotateM(modelViewM, 0, rotation, 0.0f, 0.0f, 1.0f);
+ Matrix.scaleM(modelViewM, 0, width, height, 1.0f);
+ Matrix.multiplyMM(finalM, 0, mProjMat, 0, modelViewM, 0);
+
+ // push matrix
+ GLES20.glUniformMatrix4fv(mLocMatrix, 1, false, finalM, 0);
+
+ // push positions
+ GLES20.glEnableVertexAttribArray(mLocPosition);
+ mQuadGeomBuf.position(QUAD_GEOM_POS_OFFSET);
+ GLES20.glVertexAttribPointer(mLocPosition, 3, GLES20.GL_FLOAT, false,
+ QUAD_GEOM_STRIDE, mQuadGeomBuf);
+
+ // push texture coordinates
+ GLES20.glEnableVertexAttribArray(mLocTexCoord);
+ mQuadGeomBuf.position(QUAD_GEOM_TEXCOORD_OFFSET);
+ GLES20.glVertexAttribPointer(mLocTexCoord, 2, GLES20.GL_FLOAT, false,
+ QUAD_GEOM_STRIDE, mQuadGeomBuf);
+
+ // draw
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, QUAD_GEOM_VERTEX_COUNT);
+ }
+
+ void calcCoordSystemBounds(int surfWidth, int surfHeight, RectF outBounds) {
+ if (surfWidth > surfHeight) {
+ // landscape orientation -- height is set to 1.0, width is proportional
+ outBounds.right = (surfWidth / (float) surfHeight) * 0.5f;
+ outBounds.left = -outBounds.right;
+ outBounds.top = 0.5f;
+ outBounds.bottom = -0.5f;
+ } else {
+ // portrait orientation -- width is set to 1.0, height is proportional
+ outBounds.top = (surfWidth / (float) surfHeight) * 0.5f;
+ outBounds.bottom = -outBounds.right;
+ outBounds.right = 0.5f;
+ outBounds.left = -0.5f;
+ }
+ }
+
+ public final float convertScreenX(float screenX) {
+ return mBounds.left + (screenX / mSurfWidth) * (mBounds.right - mBounds.left);
+ }
+
+ public final float convertScreenY(float screenY) {
+ float factor = 1 - (screenY / mSurfHeight);
+ return mBounds.bottom + factor * (mBounds.top - mBounds.bottom);
+ }
+
+ public final float convertScreenDeltaX(float deltaX) {
+ return convertScreenX(deltaX) - convertScreenX(0.0f);
+ }
+
+ public final float convertScreenDeltaY(float deltaY) {
+ return convertScreenY(deltaY) - convertScreenY(0.0f);
+ }
+
+ void onGLSurfaceChanged(int width, int height) {
+ Logger.d("Renderer.onGLSurfaceChanged " + width + "x" + height);
+ GLES20.glViewport(0, 0, width, height);
+ mSurfHeight = height;
+ mSurfWidth = width;
+
+ // calculate bounds for our coordinate system
+ calcCoordSystemBounds(mSurfWidth, mSurfHeight, mBounds);
+
+ // set up projection matrix
+ mProjMat = new float[16];
+ Matrix.orthoM(mProjMat, 0, mBounds.left, mBounds.right, mBounds.bottom,
+ mBounds.top, -1.0f, 1.0f);
+ }
+
+ void generateImageTextures() {
+ int count = mBitmapTextureMaker.getBitmapCount();
+ int i;
+ for (i = 0; i < count; i++) {
+ int texIndex = mBitmapTextureMaker.getTag(i);
+ generateTexture(texIndex, mBitmapTextureMaker.getBitmap(i));
+ }
+ mBitmapTextureMaker.dispose();
+ mBitmapTextureMaker = null;
+ }
+
+ void generateTextTextures() {
+ int count = mTextTextureMaker.getCount();
+ int i;
+ for (i = 0; i < count; i++) {
+ int texIndex = mTextTextureMaker.getTag(i);
+ generateTexture(texIndex, mTextTextureMaker.getBitmap(i));
+ }
+ mTextTextureMaker.dispose();
+ mTextTextureMaker = null;
+ }
+
+ private void generateTexture(int texIndex, Bitmap bmp) {
+ int[] texH = new int[1];
+ GLES20.glGenTextures(1, texH, 0);
+ TexInfo ti = mTexInfo.get(texIndex);
+ bitmapToGLTexture(texH[0], bmp);
+ ti.glTex = texH[0];
+ ti.width = bmp.getWidth();
+ ti.height = bmp.getHeight();
+ ti.aspect = bmp.getWidth() / (float) bmp.getHeight();
+ }
+
+ void bitmapToGLTexture(int texH, Bitmap bmp) {
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texH);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
+ }
+
+ // returns true if all resources are ready, false if still loading
+ boolean prepareFrame() {
+ if (mBitmapTextureMaker != null && mBitmapTextureMaker.isFinishedLoading()) {
+ // bitmap bank finished loading bitmaps -- time to convert them to textures!
+ generateImageTextures();
+ } else if (mTextTextureMaker != null && mTextTextureMaker.isFinishedLoading()) {
+ // text texture generator finished -- time to convert into textures!
+ generateTextTextures();
+ }
+
+ // we are ready if and only if there are no pending images/text to load
+ return mBitmapTextureMaker == null && mTextTextureMaker == null;
+ }
+
+ private void parseColor(int color, float[] out) {
+ long c = color;
+ out[0] = ((c & 0x00ff0000L) >>> 16) / 255.0f;
+ out[1] = ((c & 0x0000ff00L) >>> 8) / 255.0f;
+ out[2] = (c & 0x000000ffL) / 255.0f;
+ out[3] = ((c & 0xff000000L) >>> 24) / 255.0f;
+
+ // our setup requires that we premultiply the alpha. This is because we're using
+ // blending mode glBlend(GL_ONE, GL_ONE_MINUS_SRC_ALPHA), to be compatible with
+ // how GLUtils loads PNGs.
+ out[0] *= out[3];
+ out[1] *= out[3];
+ out[2] *= out[3];
+ }
+
+
+ private float[] mTmpColor = new float[4];
+
+ public void doFrame() {
+ parseColor(mClearColor, mTmpColor);
+ GLES20.glClearColor(mTmpColor[0], mTmpColor[1], mTmpColor[2], mTmpColor[3]);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
+
+ if (mBitmapTextureMaker != null || mTextTextureMaker != null) {
+ // we are still loading textures, so don't render any sprites yet
+ return;
+ }
+
+ int i, size = mSprites.size();
+ for (i = 0; i < size; i++) {
+ Sprite s = mSprites.get(i);
+ if (!s.enabled) {
+ continue;
+ }
+
+ float tintFactor = s.tintFactor;
+ TexInfo ti = null;
+ if (s.texIndex >= 0 && s.texIndex < mTexInfo.size()) {
+ ti = mTexInfo.get(s.texIndex);
+ pushTex(ti.glTex);
+ } else {
+ pushTex(0);
+ tintFactor = 1.0f;
+ }
+ parseColor(s.color, mTmpColor);
+ pushColor(mTmpColor[0], mTmpColor[1], mTmpColor[2], mTmpColor[3], tintFactor);
+
+ float width = s.width, height = s.height, x = s.x, y = s.y;
+
+ if (ti != null && ti.type == TexInfo.TYPE_IMAGE) {
+ width = s.width;
+ height = s.height;
+ if (Float.isNaN(width) && ti != null) {
+ // auto calculate width based on texture aspect ratio
+ width = s.width = s.height * ti.aspect;
+ }
+ if (Float.isNaN(height) && ti != null) {
+ // auto calculate height based on texture aspect ratio
+ height = s.height = s.width / ti.aspect;
+ }
+ } else if (ti != null && ti.type == TexInfo.TYPE_TEXT) {
+ // text images don't respect width/height -- they render at whatever
+ // size they were created, in order to respect the originally requested font size
+ width = pixelsToLogical(ti.width);
+ height = pixelsToLogical(ti.height);
+
+ // adjust x,y according to text anchor parameter
+ int horizAnchor = ti.textAnchor & TEXT_ANCHOR_HORIZ_MASK;
+ int vertAnchor = ti.textAnchor & TEXT_ANCHOR_VERT_MASK;
+ if (horizAnchor == TEXT_ANCHOR_LEFT) {
+ x += width * 0.5f;
+ } else if (horizAnchor == TEXT_ANCHOR_RIGHT) {
+ x -= width * 0.5f;
+ }
+ if (vertAnchor == TEXT_ANCHOR_TOP) {
+ y += height * 0.5f;
+ } else if (vertAnchor == TEXT_ANCHOR_BOTTOM) {
+ y -= height * 0.5f;
+ }
+ }
+
+ drawQuad(x, y, width, height, s.rotation);
+ }
+ }
+
+ private float pixelsToLogical(int pixels) {
+ float logicalWidth = mBounds.width();
+ float pixelsWidth = mSurfWidth;
+ return (pixels / (float) pixelsWidth) * logicalWidth;
+ }
+
+ public int requestImageTex(int resId, String name, int dimType, float maxDim) {
+ TexInfo ti = new TexInfo();
+ ti.type = TexInfo.TYPE_IMAGE;
+ ti.resId = resId;
+ ti.name = name;
+ ti.dimType = dimType;
+ ti.maxDim = maxDim;
+ ti.glTex = 0; // loading
+ ti.aspect = Float.NaN; // unknown for now
+ mTexInfo.add(ti);
+ return mTexInfo.size() - 1;
+ }
+
+ public int requestTextTex(int resId, String name, float fontSize, int textAnchor, int color) {
+ TexInfo ti = new TexInfo();
+ ti.type = TexInfo.TYPE_TEXT;
+ ti.resId = resId;
+ ti.fontSize = fontSize;
+ ti.glTex = 0; // loading
+ ti.aspect = Float.NaN; // unknown for now
+ ti.textAnchor = textAnchor;
+ ti.textColor = color;
+ mTexInfo.add(ti);
+ return mTexInfo.size() - 1;
+ }
+
+ public int requestTextTex(int resId, String name, float fontSize) {
+ return requestTextTex(resId, name, fontSize,
+ TEXT_ANCHOR_CENTER | TEXT_ANCHOR_MIDDLE, 0xffffffff);
+ }
+
+ public float getLeft() {
+ return mBounds.left;
+ }
+
+ public float getRight() {
+ return mBounds.right;
+ }
+
+ public float getTop() {
+ return mBounds.top;
+ }
+
+ public float getBottom() {
+ return mBounds.bottom;
+ }
+
+ public float getWidth() {
+ return mBounds.width();
+ }
+
+ public float getHeight() {
+ // height() doesn't work because in our coord system top > bottom,
+ // and RectF doesn't like that.
+ return mBounds.top - mBounds.bottom;
+ }
+
+ public void deleteTextures() {
+ int[] arr = new int[1];
+ for (TexInfo ti : mTexInfo) {
+ if (ti.glTex > 0) {
+ arr[0] = ti.glTex;
+ GLES20.glDeleteTextures(1, arr, 0);
+ ti.glTex = 0;
+ }
+ }
+ mTexInfo.clear();
+ }
+
+ public void reset() {
+ mClearColor = DEFAULT_CLEAR_COLOR;
+ deleteTextures();
+ deleteSprites();
+ }
+
+ private void refreshTextures(Context ctx) {
+ if (mTexInfo.size() > 0) {
+ for (TexInfo ti : mTexInfo) {
+ ti.glTex = 0;
+ }
+ startLoadingTexs(ctx);
+ }
+ }
+
+ void startLoadingTexs(Context ctx) {
+ mBitmapTextureMaker = new BitmapTextureMaker();
+ mTextTextureMaker = new TextTextureMaker();
+
+ int i = 0;
+ for (i = 0; i < mTexInfo.size(); i++) {
+ TexInfo ti = mTexInfo.get(i);
+ if (ti.type == TexInfo.TYPE_IMAGE) {
+ mBitmapTextureMaker.request(i, ti.resId, ti.name, ti.dimType, ti.maxDim);
+ } else if (ti.type == TexInfo.TYPE_TEXT) {
+ mTextTextureMaker.requestTex(i, ctx.getString(ti.resId), ti.fontSize, ti.textColor);
+ }
+ }
+
+ // start loading the textures in a background thread
+ mBitmapTextureMaker.startLoading(ctx);
+ mTextTextureMaker.startLoading(ctx);
+ }
+
+ void dispose() {
+ if (mBitmapTextureMaker != null) {
+ mBitmapTextureMaker.dispose();
+ mBitmapTextureMaker = null;
+ }
+ if (mTextTextureMaker != null) {
+ mTextTextureMaker.dispose();
+ mTextTextureMaker = null;
+ }
+ }
+
+ public class Sprite {
+
+ // note: NaN on width OR height means "compute automatically based on texture's
+ // aspect ratio.
+ public boolean enabled;
+ public float x, y, width, height;
+ public int texIndex;
+ public int color;
+ public float tintFactor;
+ public float rotation;
+
+ public Sprite() {
+ clear();
+ }
+
+ public Sprite clear() {
+ x = y = 0.0f;
+ width = height = 1.0f;
+ texIndex = -1;
+ color = 0xff000080;
+ tintFactor = 1.0f;
+ enabled = true;
+ rotation = 0.0f;
+ return this;
+ }
+ }
+
+ public Sprite createSprite() {
+ Sprite s;
+ if (mSpriteRecycleBin.size() > 0) {
+ s = mSpriteRecycleBin.remove(mSpriteRecycleBin.size() - 1);
+ } else {
+ s = new Sprite();
+ }
+ mSprites.add(s);
+ return s;
+ }
+
+ public void deleteSprite(Sprite s) {
+ s.clear();
+ mSprites.remove(s);
+ mSpriteRecycleBin.add(s);
+ }
+
+ public void deleteSprites() {
+ int i;
+ for (i = 0; i < mSprites.size(); i++) {
+ mSpriteRecycleBin.add(mSprites.get(i).clear());
+ }
+ mSprites.clear();
+ }
+
+ public float getRelativePos(int relativeTo, float delta) {
+ return delta + (relativeTo == REL_RIGHT ? mBounds.right :
+ relativeTo == REL_LEFT ? mBounds.left :
+ relativeTo == REL_TOP ? mBounds.top :
+ relativeTo == REL_BOTTOM ? mBounds.bottom : 0.0f);
+ }
+
+ public void bringToFront(Sprite sp) {
+ int idx = mSprites.indexOf(sp);
+ if (idx >= 0 && idx < mSprites.size()) {
+ mSprites.remove(idx);
+ mSprites.add(sp);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Scene.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Scene.java
new file mode 100644
index 000000000..08985ecf9
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/Scene.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+public class Scene {
+
+ public void onScreenResized(int width, int height) {
+ }
+
+ public void doStandbyFrame(float deltaT) {
+ }
+
+ public void doFrame(float deltaT) {
+ }
+
+ public void onInstall() {
+ }
+
+ public void onUninstall() {
+ }
+
+ public void onPointerDown(int pointerId, float x, float y) {
+ }
+
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ }
+
+ public void onPointerUp(int pointerId, float x, float y) {
+ }
+
+ public void onKeyDown(int keyCode, int repeatCount) {
+ }
+
+ public void onKeyUp(int keyCode) {
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SceneManager.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SceneManager.java
new file mode 100644
index 000000000..9b674dbba
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SceneManager.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+public class SceneManager {
+
+ private static SceneManager instance = new SceneManager();
+ private Renderer mRenderer = new Renderer();
+ private SoundManager mSoundManager = null;
+ private Scene mCurScene = null;
+ private Scene mNewScene = null;
+ private long mLastFrameTime = -1;
+ private boolean mHasGL = false;
+ private Context mAppContext = null;
+
+ // reference to Activity, if it's in the resumed state -- otherwise null
+ private WeakReference mActivity = new WeakReference(null);
+ private boolean mActivityResumed = false;
+ private boolean mActivityHasFocus = false;
+
+ // queue of MotionEvents to process from the game thread
+ private ArrayList mMotionEventQueue = new ArrayList(32);
+ private ArrayList mMotionEventRecycle = new ArrayList(32);
+
+ // this flag is raised by the UI thread when it adds something to mMotionEventQueue
+ // and lowered by the game thread when it processes the motion event queue. This flag
+ // should only be modified when mMotionEventQueue is locked; it can be read without
+ // locking.
+ private volatile boolean mCheckMotionEvents = false;
+
+ // last x, y of pointer, keyed by pointer ID
+ private SparseArray mLastTouchCoords = new SparseArray();
+
+ // recycle bin of PointF objects
+ private ArrayList mPointRecycleBin = new ArrayList();
+
+ private SceneManager() {
+ }
+
+ public static SceneManager getInstance() {
+ return instance;
+ }
+
+ void onGLSurfaceCreated(Context ctx) {
+ mHasGL = true;
+ mAppContext = ctx.getApplicationContext();
+ mRenderer.onGLSurfaceCreated(mAppContext);
+ if (mSoundManager == null) {
+ mSoundManager = new SoundManager(ctx);
+ }
+ }
+
+ void onGLSurfaceChanged(int width, int height) {
+ mRenderer.onGLSurfaceChanged(width, height);
+ if (mCurScene != null) {
+ mCurScene.onScreenResized(width, height);
+ }
+ }
+
+ private void installNewScene() {
+ if (mCurScene != null) {
+ mCurScene.onUninstall();
+ mRenderer.reset();
+ mSoundManager.reset();
+ }
+ mCurScene = mNewScene;
+ mNewScene = null;
+ if (mCurScene != null) {
+ mCurScene.onInstall();
+ mRenderer.startLoadingTexs(mAppContext);
+ }
+ }
+
+ public void onPause() {
+ mActivityResumed = false;
+ if (mSoundManager != null) {
+ mSoundManager.pause();
+ }
+ mActivity.clear();
+ }
+
+ public void onResume(Activity activity) {
+ mActivityResumed = true;
+ mActivity = new WeakReference(activity);
+ if (mSoundManager != null && mActivityHasFocus) {
+ mSoundManager.resume();
+ }
+ }
+
+ public Activity getActivity() {
+ return mActivity.get();
+ }
+
+ public void onFocusChanged(boolean focus) {
+ mActivityHasFocus = focus;
+ if (!focus) {
+ mSoundManager.pause();
+ } else if (mActivityResumed && mSoundManager != null) {
+ mSoundManager.resume();
+ }
+ }
+
+ public boolean shouldBePlaying() {
+ return mActivityResumed && mActivityHasFocus;
+ }
+
+ public Scene getCurrentScene() {
+ return mCurScene;
+ }
+
+ void onDrawFrame() {
+ if (!mHasGL) {
+ Logger.w("Ignoring request to do frame without a GL surface.");
+ return;
+ }
+ if (mNewScene != null) {
+ installNewScene();
+ }
+ if (mCurScene != null) {
+ if (mLastFrameTime < 0) {
+ mLastFrameTime = System.currentTimeMillis();
+ }
+ float deltaT = (System.currentTimeMillis() - mLastFrameTime) * 0.001f;
+ mLastFrameTime = System.currentTimeMillis();
+ if (mRenderer.prepareFrame() && mSoundManager.isReady()) {
+ mCurScene.doFrame(deltaT);
+ } else {
+ mCurScene.doStandbyFrame(deltaT);
+ }
+ }
+ mRenderer.doFrame();
+
+ // process touch events
+ if (mCheckMotionEvents) {
+ processMotionEvents();
+ }
+ }
+
+ public void enableDebugLog(boolean enable) {
+ Logger.enableDebugLog(enable);
+ }
+
+ public void requestNewScene(Scene c) {
+ mNewScene = c;
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return false;
+ } else {
+ processKeyEvent(keyCode, event);
+ return true;
+ }
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return false;
+ } else {
+ processKeyEvent(keyCode, event);
+ return true;
+ }
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ // we are running on the UI thread, so deliver the event to the queue,
+ // where the game thread will pick it up to process
+ synchronized (mMotionEventQueue) {
+ int action = event.getActionMasked();
+
+ // get updates about each pointer in the gesture
+ int i;
+ for (i = 0; i < event.getPointerCount(); i++) {
+ int pointerId = event.getPointerId(i);
+ float x = event.getX(i);
+ float y = event.getY(i);
+
+ // figure out delta from last touch event
+ float deltaX = x - getLastTouchX(pointerId, x);
+ float deltaY = y - getLastTouchY(pointerId, y);
+
+ // queue the motion event
+ queueMotionEvent(MotionEvent.ACTION_MOVE, pointerId, x, y, deltaX, deltaY);
+
+ // update last touch coordinates
+ setLastTouchCoords(pointerId, x, y);
+ }
+
+ // figure out if a pointer went up or down
+ int id;
+ PointF point;
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ id = event.getPointerId(event.getActionIndex());
+ forgetLastTouchCoords(id);
+ queueMotionEvent(MotionEvent.ACTION_UP, id, event.getX(), event.getY(),
+ 0.0f, 0.0f);
+ break;
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ id = event.getPointerId(event.getActionIndex());
+ setLastTouchCoords(id, event.getX(), event.getY());
+ queueMotionEvent(MotionEvent.ACTION_DOWN, id, event.getX(), event.getY(),
+ 0.0f, 0.0f);
+ break;
+ }
+ }
+ return true;
+ }
+
+ private float getLastTouchX(int pointerId, float defaultX) {
+ PointF pt = mLastTouchCoords.get(pointerId, null);
+ return pt != null ? pt.x : defaultX;
+ }
+
+ private float getLastTouchY(int pointerId, float defaultY) {
+ PointF pt = mLastTouchCoords.get(pointerId, null);
+ return pt != null ? pt.y : defaultY;
+ }
+
+ private void setLastTouchCoords(int pointerId, float x, float y) {
+ PointF pt = mLastTouchCoords.get(pointerId, null);
+ if (pt == null) {
+ pt = allocPointF();
+ }
+ pt.x = x;
+ pt.y = y;
+ mLastTouchCoords.put(pointerId, pt);
+ }
+
+ private void forgetLastTouchCoords(int pointerId) {
+ PointF pt = mLastTouchCoords.get(pointerId, null);
+ if (pt != null) {
+ mLastTouchCoords.remove(pointerId);
+ recyclePointF(pt);
+ }
+ }
+
+ private PointF allocPointF() {
+ if (mPointRecycleBin.size() > 0) {
+ PointF p = mPointRecycleBin.remove(mPointRecycleBin.size() - 1);
+ p.x = p.y = 0.0f;
+ return p;
+ }
+ return new PointF();
+ }
+
+ private void recyclePointF(PointF p) {
+ mPointRecycleBin.add(p);
+ }
+
+ private void queueMotionEvent(int action, int pointerId, float screenX, float screenY,
+ float deltaX, float deltaY) {
+ OurMotionEvent e = mMotionEventRecycle.size() > 0 ?
+ mMotionEventRecycle.remove(mMotionEventRecycle.size() - 1) :
+ new OurMotionEvent();
+ e.action = action;
+ e.pointerId = pointerId;
+ e.screenX = screenX;
+ e.screenY = screenY;
+ e.deltaX = deltaX;
+ e.deltaY = deltaY;
+ mMotionEventQueue.add(e);
+ mCheckMotionEvents = true;
+ }
+
+ public Renderer getRenderer() {
+ return mRenderer;
+ }
+
+ public SoundManager getSoundManager() {
+ return mSoundManager;
+ }
+
+ ArrayList mTmpMotionEvent = new ArrayList(32);
+
+ private void processMotionEvents() {
+ int i;
+
+ // move array items to our temporary array so we can unlock the original
+ synchronized (mMotionEventQueue) {
+ for (i = 0; i < mMotionEventQueue.size(); i++) {
+ mTmpMotionEvent.add(mMotionEventQueue.get(i));
+ }
+ mMotionEventQueue.clear();
+ mCheckMotionEvents = false;
+ }
+
+ // process the motion events
+ for (i = 0; i < mTmpMotionEvent.size(); i++) {
+ processMotionEvent(mTmpMotionEvent.get(i));
+ }
+
+ // recycle the objects
+ synchronized (mMotionEventQueue) {
+ for (i = 0; i < mTmpMotionEvent.size(); i++) {
+ mMotionEventRecycle.add(mTmpMotionEvent.get(i));
+ }
+ mTmpMotionEvent.clear();
+ }
+ }
+
+ private void processMotionEvent(OurMotionEvent event) {
+ if (mCurScene == null) {
+ return;
+ }
+
+ // convert the screen coordinates to our standard coordinate system
+ float x = mRenderer.convertScreenX(event.screenX);
+ float y = mRenderer.convertScreenY(event.screenY);
+ float deltaX = mRenderer.convertScreenDeltaX(event.deltaX);
+ float deltaY = mRenderer.convertScreenDeltaY(event.deltaY);
+
+ switch (event.action) {
+ case MotionEvent.ACTION_DOWN:
+ mCurScene.onPointerDown(event.pointerId, x, y);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mCurScene.onPointerMove(event.pointerId, x, y, deltaX, deltaY);
+ break;
+ case MotionEvent.ACTION_UP:
+ mCurScene.onPointerUp(event.pointerId, x, y);
+ break;
+ }
+ }
+
+ private void processKeyEvent(int keyCode, KeyEvent event) {
+ if (mCurScene == null) {
+ return;
+ }
+
+ // convert the screen coordinates to our standard coordinate system
+
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ mCurScene.onKeyDown(keyCode, event.getRepeatCount());
+ break;
+ case KeyEvent.ACTION_UP:
+ mCurScene.onKeyUp(keyCode);
+ break;
+ }
+ }
+
+ private class OurMotionEvent {
+
+ int action;
+ int pointerId;
+ float screenX, screenY;
+ float deltaX, deltaY;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ShaderSource.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ShaderSource.java
new file mode 100644
index 000000000..3164753ce
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ShaderSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+public class ShaderSource {
+
+ private static final String COMMON_DECLS =
+ "precision mediump float; \n" +
+ "uniform mat4 u_Matrix; \n" +
+ "uniform vec4 u_Color; \n" +
+ "uniform float u_TintFactor; \n" +
+ "uniform sampler2D u_Sampler; \n " +
+ "varying vec4 v_Color; \n" +
+ "varying vec2 v_TexCoord; \n";
+
+ public static final String VERTEX_SHADER = COMMON_DECLS +
+ "attribute vec4 a_Position; \n" +
+ "attribute vec2 a_TexCoord; \n" +
+ "void main() \n" +
+ "{ \n" +
+ " v_Color = u_Color; \n" +
+ " v_TexCoord = a_TexCoord; \n" +
+ " gl_Position = u_Matrix * a_Position; \n" +
+ "} \n";
+
+ public static final String FRAG_SHADER =
+ COMMON_DECLS +
+ "void main() \n" +
+ "{ \n" +
+ " vec4 c = mix(texture2D(u_Sampler, v_TexCoord), u_Color, u_TintFactor);\n" +
+ " gl_FragColor = c;\n" +
+ "}\n";
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SmoothValue.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SmoothValue.java
new file mode 100644
index 000000000..a95b391a8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SmoothValue.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+public class SmoothValue {
+
+ private float mValue = 0.0f;
+ private float mTarget = 0.0f;
+ private float mChangeSpeed = 1.0f;
+ private boolean mOnTarget = false;
+ private float mMin = Float.NEGATIVE_INFINITY;
+ private float mMax = Float.POSITIVE_INFINITY;
+ private int mSamples = 1;
+
+ public SmoothValue() {
+ }
+
+ public SmoothValue(float initialValue, float changeSpeed) {
+ init(initialValue, changeSpeed, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 1);
+ }
+
+ public SmoothValue(float initialValue, float changeSpeed, float min, float max) {
+ init(initialValue, changeSpeed, min, max, 1);
+ }
+
+ public SmoothValue(float initialValue, float changeSpeed, float min, float max, int samples) {
+ init(initialValue, changeSpeed, min, max, samples);
+ }
+
+ private void init(float initialValue, float changeSpeed, float min, float max, int samples) {
+ mValue = initialValue;
+ mChangeSpeed = changeSpeed;
+ mMin = min;
+ mMax = max;
+ mSamples = samples;
+ }
+
+ public void setTarget(float target) {
+ mTarget = target;
+ mOnTarget = false;
+ }
+
+ public void update(float deltaT) {
+ float displac = deltaT * mChangeSpeed;
+ float value;
+ if (Math.abs(mValue - mTarget) <= displac) {
+ value = mTarget;
+ mOnTarget = true;
+ } else if (mTarget > mValue) {
+ value = mValue + displac;
+ } else {
+ value = mValue - displac;
+ }
+
+ if (mSamples > 0) {
+ mValue = (mValue * mSamples + value) / (mSamples + 1);
+ } else {
+ mValue = value;
+ }
+ mValue = mValue < mMin ? mMin : mValue > mMax ? mMax : mValue;
+ }
+
+ public float getValue() {
+ return mValue;
+ }
+
+ public void setValue(float value) {
+ mValue = value;
+ }
+
+ public float getTarget() {
+ return mTarget;
+ }
+
+ public boolean isOnTarget() {
+ return mOnTarget;
+ }
+
+ public float getMin() {
+ return mMin;
+ }
+
+ public float getMax() {
+ return mMax;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SoundManager.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SoundManager.java
new file mode 100644
index 000000000..39690ff85
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/SoundManager.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+
+import java.io.IOException;
+
+public class SoundManager implements MediaPlayer.OnPreparedListener,
+ SoundPool.OnLoadCompleteListener {
+
+ MediaPlayer mBgmMediaPlayer = null;
+ AssetFileDescriptor mBgmFileDescriptor = null;
+ boolean mBgmLoading = false;
+ Context mAppContext;
+ boolean mPaused = false;
+ boolean mWantBgm = true;
+
+ SoundPool mSoundPool = null;
+ int mSoundsLoading = 0; // how many sounds are loading in the SoundPool
+
+ static final int MAX_STREAMS = 4;
+ static final int STREAM_TYPE = AudioManager.STREAM_MUSIC;
+ static final int SRC_QUALITY = 0;
+ static final int DEFAULT_PRIORITY = 1;
+ static final float DEFAULT_VOLUME = 0.6f;
+ static final float DEFAULT_BGM_VOLUME = 0.5f;
+
+ public SoundManager(Context ctx) {
+ mAppContext = ctx.getApplicationContext();
+ mSoundPool = new SoundPool(MAX_STREAMS, STREAM_TYPE, SRC_QUALITY);
+ mSoundPool.setOnLoadCompleteListener(this);
+ }
+
+ public void requestBackgroundMusic(String assetsFileName) {
+ try {
+ mBgmFileDescriptor = mAppContext.getAssets().openFd(assetsFileName);
+ mBgmMediaPlayer = new MediaPlayer();
+ mBgmMediaPlayer.setDataSource(mBgmFileDescriptor.getFileDescriptor(),
+ mBgmFileDescriptor.getStartOffset(),
+ mBgmFileDescriptor.getDeclaredLength());
+ mBgmMediaPlayer.setOnPreparedListener(this);
+ mBgmLoading = true;
+ mBgmMediaPlayer.prepareAsync();
+ } catch (IOException ex) {
+ Logger.e("Error loading background music from asset file: " + assetsFileName);
+ ex.printStackTrace();
+ return;
+ }
+ }
+
+ public int requestSfx(int resId) {
+ mSoundsLoading++;
+ return mSoundPool.load(mAppContext, resId, DEFAULT_PRIORITY);
+ }
+
+ public void playSfx(int soundId) {
+ mSoundPool.play(soundId, DEFAULT_VOLUME, DEFAULT_VOLUME, DEFAULT_PRIORITY, 0, 1.0f);
+ }
+
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ if (mBgmFileDescriptor != null) {
+ try {
+ mBgmFileDescriptor.close();
+ } catch (IOException ex) {
+ Logger.e("Error closing bgm file descriptor:");
+ ex.printStackTrace();
+ }
+ mBgmFileDescriptor = null;
+ }
+ mBgmLoading = false;
+ mBgmMediaPlayer.setVolume(DEFAULT_BGM_VOLUME, DEFAULT_BGM_VOLUME);
+ mBgmMediaPlayer.setLooping(true);
+ updateBgm();
+ }
+
+ public boolean isReady() {
+ return !mBgmLoading && mSoundsLoading <= 0;
+ }
+
+ private void updateBgm() {
+ boolean shouldPlay = !mPaused && mWantBgm;
+ if (mBgmMediaPlayer != null) {
+ if (shouldPlay && !mBgmMediaPlayer.isPlaying()) {
+ mBgmMediaPlayer.start();
+ } else if (!shouldPlay && mBgmMediaPlayer.isPlaying()) {
+ mBgmMediaPlayer.pause();
+ }
+ }
+ }
+
+ public void pause() {
+ mPaused = true;
+ updateBgm();
+ }
+
+ public void resume() {
+ mPaused = false;
+ updateBgm();
+ }
+
+ public void enableBgm(boolean enable) {
+ mWantBgm = enable;
+ updateBgm();
+ }
+
+ public void reset() {
+ if (mBgmMediaPlayer != null) {
+ if (mBgmMediaPlayer.isPlaying()) {
+ mBgmMediaPlayer.stop();
+ }
+ mBgmMediaPlayer = null;
+ }
+ mBgmLoading = false;
+ mWantBgm = true;
+ }
+
+ public void dispose() {
+ if (mBgmMediaPlayer != null && mBgmMediaPlayer.isPlaying()) {
+ mBgmMediaPlayer.stop();
+ }
+ }
+
+ @Override
+ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+ mSoundsLoading--;
+ if (status != 0) {
+ Logger.e("Error loading SFX into SoundPool, sample " + sampleId +
+ ", status " + status);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/TextTextureMaker.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/TextTextureMaker.java
new file mode 100644
index 000000000..aa9cadc98
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/TextTextureMaker.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+public class TextTextureMaker implements Runnable {
+
+ private final static int PADDING_LEFT = 2;
+ private final static int PADDING_RIGHT = 5;
+ private final static int PADDING_BOTTOM = 2;
+ private final static int PADDING_TOP = 2;
+
+ private final static int SUPERSAMPLING = 2;
+
+ private class Entry {
+
+ int tag;
+ String text;
+ float fontSize;
+ Bitmap bitmap = null;
+ int color;
+ }
+
+ ArrayList mEntries = new ArrayList();
+ boolean mStartedLoading = false;
+ boolean mFinishedLoading = false;
+ Context mCtx = null;
+
+ public TextTextureMaker() {
+ }
+
+ void requestTex(int tag, String text, float fontSize, int color) {
+ Entry e = new Entry();
+ e.tag = tag;
+ e.text = text;
+ e.fontSize = fontSize;
+ e.color = color;
+ mEntries.add(e);
+ }
+
+ void startLoading(Context ctx) {
+ if (mStartedLoading) {
+ Logger.e("TextTextureMaker.startLoading() called twice!");
+ return;
+ }
+ mCtx = ctx.getApplicationContext();
+ mStartedLoading = true;
+ (new Thread(this)).start();
+ }
+
+ boolean isFinishedLoading() {
+ return mFinishedLoading;
+ }
+
+ int getCount() {
+ return mEntries.size();
+ }
+
+ Bitmap getBitmap(int index) {
+ if (!mFinishedLoading) {
+ Logger.e("Can't call TextTextureMaker.getBitmap before load is finished!");
+ return null;
+ }
+ return (index >= 0 && index < mEntries.size()) ? mEntries.get(index).bitmap : null;
+ }
+
+ int getTag(int index) {
+ return (index >= 0 && index < mEntries.size()) ? mEntries.get(index).tag : null;
+ }
+
+ @Override
+ public void run() {
+ for (Entry e : mEntries) {
+ makeBitmapForEntry(e);
+ }
+
+ mFinishedLoading = true;
+ }
+
+ private void makeBitmapForEntry(Entry e) {
+ Logger.d("Making bitmap for text '" + e.text + "', font size " + e.fontSize);
+ Paint p = new Paint();
+ Rect bounds = new Rect();
+ WindowManager wm = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE);
+ float fontUnit = SUPERSAMPLING * wm.getDefaultDisplay().getWidth() / 1000.0f;
+
+ p.setColor(e.color);
+ p.setTextSize(e.fontSize * fontUnit);
+ p.getTextBounds(e.text, 0, e.text.length(), bounds);
+
+ Logger.d("Text bounds: " + bounds.toString());
+
+ int width = bounds.width() + PADDING_LEFT + PADDING_RIGHT;
+ int height = bounds.height() + PADDING_TOP + PADDING_BOTTOM;
+ int textX = -bounds.left + PADDING_LEFT;
+ int textY = -bounds.top + PADDING_TOP;
+
+ Logger.d("Bitmap will be " + width + "x" + height + ", offset will be " + textX + "," +
+ textY);
+
+ Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(bmp);
+ bmp.eraseColor(0);
+ c.drawColor(0);
+ c.drawText(e.text, textX, textY, p);
+
+ if (SUPERSAMPLING > 1) {
+ e.bitmap = Bitmap.createScaledBitmap(bmp, bmp.getWidth() / SUPERSAMPLING,
+ bmp.getHeight() / SUPERSAMPLING, true);
+ bmp.recycle();
+ } else {
+ e.bitmap = bmp;
+ }
+ }
+
+ public void dispose() {
+ mFinishedLoading = mStartedLoading = false;
+ for (Entry e : mEntries) {
+ if (e.bitmap != null) {
+ e.bitmap.recycle();
+ e.bitmap = null;
+ }
+ }
+ mEntries.clear();
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/GameObject.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/GameObject.java
new file mode 100644
index 000000000..410bf1bd8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/GameObject.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine.game;
+
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+// this class is final for performance reasons
+public final class GameObject {
+
+ private World mWorld;
+
+ // position, velocity, acceleration
+ public float x = 0.0f;
+ public float y = 0.0f;
+ public float velX = 0.0f;
+ public float velY = 0.0f;
+ public float accX = 0.0f;
+ public float accY = 0.0f;
+
+ // collides?
+ public boolean collides = false;
+
+ // if it collides, width and height of collider box (centered on x,y)
+ public float collBoxWidth = 0.0f;
+ public float collBoxHeight = 0.0f;
+
+ // type -- the meaning of this is up to the game developer
+ public int type = 0;
+
+ // for developer use
+ public int ivar[] = new int[16];
+ public float fvar[] = new float[16];
+ public boolean bvar[] = new boolean[16];
+
+ // flag that indicates that this GameObject should be deleted asap
+ public boolean dead = false;
+
+ // countdown to this object's death
+ public float timeToLive = Float.POSITIVE_INFINITY;
+
+ // sprites
+ public ArrayList mSprites = new ArrayList();
+
+ // texture that shows collider size (for debug purposes)
+ private int mColliderSpriteIdx = -1;
+
+ public final void update(float deltaT) {
+ if (dead) {
+ return;
+ }
+ if ((timeToLive -= deltaT) <= 0.0f) {
+ dead = true;
+ return;
+ }
+
+ velX += accX * deltaT;
+ velY += accY * deltaT;
+ displaceBy(velX * deltaT, velY * deltaT);
+
+ if (mColliderSpriteIdx >= 0) {
+ mSprites.get(mColliderSpriteIdx).enabled = (System.currentTimeMillis() % 200) < 100;
+ }
+ }
+
+ // only created by the World (if you want a GameObject, ask the World for one)
+ GameObject(World world) {
+ mWorld = world;
+ clear();
+ }
+
+ // only called by the World
+ void clear() {
+ deleteSprites();
+ dead = false;
+ timeToLive = Float.POSITIVE_INFINITY;
+ x = y = velX = velY = accX = accY = 0.0f;
+ collides = false;
+ collBoxHeight = collBoxWidth = 0.0f;
+ type = 0;
+ Arrays.fill(ivar, 0);
+ Arrays.fill(fvar, 0.0f);
+ Arrays.fill(bvar, false);
+ }
+
+ public void displaceBy(float dx, float dy) {
+ x += dx;
+ y += dy;
+
+ // displace sprites
+ int i;
+ for (i = 0; i < mSprites.size(); i++) {
+ mSprites.get(i).x += dx;
+ mSprites.get(i).y += dy;
+ }
+ }
+
+ public void displaceTo(float toX, float toY) {
+ displaceBy(toX - x, toY - y);
+ }
+
+ public void displaceTowards(float targetX, float targetY, float maxDisplacement) {
+ if (distanceTo(targetX, targetY) <= maxDisplacement) {
+ displaceTo(targetX, targetY);
+ } else {
+ float velX = targetX - x;
+ float velY = targetY - y;
+ float modulus = (float) Math.sqrt(velX * velX + velY * velY);
+ displaceBy(velX * maxDisplacement / modulus, velY * maxDisplacement / modulus);
+ }
+ }
+
+ public float distanceTo(float x, float y) {
+ return (float) Math.sqrt((this.x - x) * (this.x - x) + (this.y - y) * (this.y - y));
+ }
+
+ public int addSprite() {
+ // create renderer sprite
+ Renderer.Sprite s = mWorld.getRenderer().createSprite();
+ s.x = x;
+ s.y = y;
+ mSprites.add(s);
+ return mSprites.size() - 1;
+ }
+
+ public Renderer.Sprite getSprite(int i) {
+ return i >= 0 && i < mSprites.size() ? mSprites.get(i) : null;
+ }
+
+ public int getSpriteCount() {
+ return mSprites.size();
+ }
+
+ public void deleteSprites() {
+ int i;
+ for (i = 0; i < mSprites.size(); i++) {
+ mWorld.getRenderer().deleteSprite(mSprites.get(i));
+ }
+ mSprites.clear();
+ }
+
+ public void setBoxCollider(float width, float height) {
+ collBoxHeight = height;
+ collBoxWidth = width;
+ collides = true;
+ }
+
+ public void debugShowCollider() {
+ Renderer.Sprite s = getSprite(mColliderSpriteIdx = addSprite());
+ s.x = x;
+ s.y = y;
+ s.width = collBoxWidth;
+ s.height = collBoxHeight;
+ s.color = 0xffff0000;
+ s.tintFactor = 1.0f;
+ s.texIndex = -1;
+ }
+
+ public void hide() {
+ show(false);
+ }
+
+ public void show() {
+ show(true);
+ }
+
+ public void show(boolean show) {
+ int i;
+ for (i = 0; i < mSprites.size(); i++) {
+ Renderer.Sprite s = mSprites.get(i);
+ s.enabled = show;
+ }
+ }
+
+ public void bringToFront() {
+ int i;
+ for (i = 0; i < mSprites.size(); i++) {
+ mWorld.getRenderer().bringToFront(mSprites.get(i));
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/World.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/World.java
new file mode 100644
index 000000000..9ea2bfaf9
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/game/World.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine.game;
+
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+
+import android.graphics.RectF;
+
+import java.util.ArrayList;
+
+public final class World {
+
+ // renderer
+ Renderer mRenderer;
+
+ public ArrayList gameObjects = new ArrayList();
+
+ // recycle bin of objects for reuse
+ private ArrayList mRecycleBin = new ArrayList(64);
+
+ public World(Renderer r) {
+ mRenderer = r;
+ }
+
+ private GameObject newGameObject(int type, float x, float y, boolean createSprite,
+ int texIndex, int color, float tintFactor, float spriteWidth, float spriteHeight) {
+ GameObject o;
+ if (mRecycleBin.isEmpty()) {
+ o = new GameObject(this);
+ } else {
+ o = mRecycleBin.remove(mRecycleBin.size() - 1);
+ }
+ o.x = x;
+ o.y = y;
+ o.type = type;
+
+ if (createSprite) {
+ Renderer.Sprite s = o.getSprite(o.addSprite());
+ s.texIndex = texIndex;
+ s.tintFactor = tintFactor;
+ s.color = color;
+ s.width = spriteWidth;
+ s.height = spriteHeight;
+ }
+
+ gameObjects.add(o);
+ return o;
+ }
+
+ public GameObject newGameObject(int type, float x, float y) {
+ return newGameObject(type, x, y, false, -1, 0, 0.0f, 0.0f, 0.0f);
+ }
+
+ public GameObject newGameObjectWithImage(int type, float x, float y,
+ int texIndex, float spriteWidth, float spriteHeight) {
+ return newGameObject(type, x, y, true, texIndex, 0, 0.0f, spriteWidth, spriteHeight);
+ }
+
+ public GameObject newGameObjectWithColor(int type, float x, float y, int color,
+ float spriteWidth, float spriteHeight) {
+ return newGameObject(type, x, y, true, -1, color, 1.0f, spriteWidth, spriteHeight);
+ }
+
+ public void doFrame(float deltaT) {
+ int i;
+
+ // we iterate backwards so that the iteration is not affected by things
+ // getting added as part of the update process
+ for (i = gameObjects.size() - 1; i >= 0; --i) {
+ gameObjects.get(i).update(deltaT);
+ }
+
+ // remove dead objects
+ for (i = gameObjects.size() - 1; i >= 0; --i) {
+ if (gameObjects.get(i).dead) {
+ // more efficient than remove() because this doesn't cause
+ // the whole array to shift. In other words, remove() is O(n),
+ // this method is O(1):
+ GameObject deleted = gameObjects.get(i);
+ int last = gameObjects.size() - 1;
+ gameObjects.set(i, gameObjects.get(last));
+ gameObjects.remove(last);
+
+ // recycle it!
+ deleted.clear();
+ mRecycleBin.add(deleted);
+ }
+ }
+ }
+
+ public boolean detectCollisions(GameObject object,
+ ArrayList result, boolean clear) {
+ int i;
+ boolean found = false;
+
+ if (clear) {
+ result.clear();
+ }
+ if (object.dead || !object.collides) {
+ return false;
+ }
+ for (i = 0; i < gameObjects.size(); i++) {
+ GameObject o = gameObjects.get(i);
+ if (!o.dead && o != object && o.collides && objectsCollide(object, o)) {
+ found = true;
+ result.add(o);
+ }
+ }
+ return found;
+ }
+
+ RectF mRect1 = new RectF();
+ RectF mRect2 = new RectF();
+
+ private boolean objectsCollide(GameObject a, GameObject b) {
+ if (a.dead || b.dead || !a.collides || !b.collides) {
+ return false;
+ }
+ getColliderBounds(a, mRect1);
+ getColliderBounds(b, mRect2);
+ return mRect1.intersect(mRect2);
+ }
+
+ private void getColliderBounds(GameObject obj, RectF result) {
+ result.left = obj.x - obj.collBoxWidth * 0.5f;
+ result.right = obj.x + obj.collBoxWidth * 0.5f;
+ result.top = obj.y - obj.collBoxHeight * 0.5f;
+ result.bottom = obj.y + obj.collBoxHeight * 0.5f;
+ }
+
+ public Renderer getRenderer() {
+ return mRenderer;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Button.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Button.java
new file mode 100644
index 000000000..fefd79099
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Button.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine.ui;
+
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+
+import java.util.ArrayList;
+
+public class Button extends Widget {
+
+ private boolean mVisible = true;
+ private boolean mArmed = false;
+ private Renderer mRenderer;
+ private float mX, mY, mWidth, mHeight;
+
+ // set of sprites to show when in normal (non highlight) mode
+ private ArrayList mNormalSprites = new ArrayList();
+
+ // set of sprites to show when in highlighted mode
+ private ArrayList mHighlightSprites = new ArrayList();
+
+ private WidgetTriggerListener mListener = null;
+ private int mTriggerMessage = 0;
+
+ public Button(Renderer renderer, float x, float y, float width, float height) {
+ mX = x;
+ mY = y;
+ mWidth = width;
+ mHeight = height;
+ mRenderer = renderer;
+ }
+
+ private Renderer.Sprite makeFlatSprite(int color) {
+ Renderer.Sprite sp = mRenderer.createSprite();
+ sp.color = color;
+ sp.x = mX;
+ sp.y = mY;
+ sp.width = mWidth;
+ sp.height = mHeight;
+ sp.tintFactor = 0.0f;
+ return sp;
+ }
+
+ public void addFlatBackground(int normalColor, int highlightColor) {
+ mNormalSprites.add(makeFlatSprite(normalColor));
+ mHighlightSprites.add(makeFlatSprite(highlightColor));
+ updateSprites();
+ }
+
+ public void addTex(boolean showWhenNormal, boolean showWhenHighlighted,
+ int texIndex, float deltaX, float deltaY, float width, float height) {
+
+ if (!showWhenHighlighted && !showWhenNormal) {
+ return;
+ }
+
+ Renderer.Sprite sp = mRenderer.createSprite();
+ sp.x = mX + deltaX;
+ sp.y = mY + deltaY;
+ sp.width = width;
+ sp.height = height;
+ sp.texIndex = texIndex;
+ sp.tintFactor = 0.0f;
+
+ if (showWhenHighlighted) {
+ mHighlightSprites.add(sp);
+ }
+
+ if (showWhenNormal) {
+ mNormalSprites.add(sp);
+ }
+
+ updateSprites();
+ }
+
+ public void addNormalTex(int texIndex, float deltaX, float deltaY, float width, float height) {
+ addTex(true, false, texIndex, deltaX, deltaY, width, height);
+ }
+
+ public void addNormalTex(int texIndex) {
+ addNormalTex(texIndex, 0.0f, 0.0f, mWidth, mHeight);
+ }
+
+ public void addHighlightTex(int texIndex, float deltaX, float deltaY, float width,
+ float height) {
+ addTex(false, true, texIndex, deltaX, deltaY, width, height);
+ }
+
+ public void addHighlightTex(int texIndex) {
+ addHighlightTex(texIndex, 0.0f, 0.0f, mWidth, mHeight);
+ }
+
+ public void addTex(int texIndex, float deltaX, float deltaY, float width, float height) {
+ addTex(true, true, texIndex, deltaX, deltaY, width, height);
+ }
+
+ public void addTex(int texIndex, float width, float height) {
+ addTex(true, true, texIndex, 0.0f, 0.0f, width, height);
+ }
+
+ public void addTex(int texIndex) {
+ addTex(texIndex, mWidth, mHeight);
+ }
+
+ private void enableSprites(ArrayList sprites, boolean enable) {
+ int i;
+ for (i = 0; i < sprites.size(); i++) {
+ sprites.get(i).enabled = enable;
+ }
+ }
+
+ private void updateSprites() {
+ if (!mVisible) {
+ enableSprites(mNormalSprites, false);
+ enableSprites(mHighlightSprites, false);
+ } else if (mArmed) {
+ enableSprites(mNormalSprites, false);
+ enableSprites(mHighlightSprites, true);
+ } else {
+ enableSprites(mHighlightSprites, false);
+ enableSprites(mNormalSprites, true);
+ }
+ }
+
+ public void setClickListener(WidgetTriggerListener listener, int message) {
+ mListener = listener;
+ mTriggerMessage = message;
+ }
+
+ public void hide() {
+ show(false);
+ }
+
+ public void show() {
+ show(true);
+ }
+
+ public void show(boolean show) {
+ mVisible = show;
+ updateSprites();
+ }
+
+ // Simulate button click event.
+ public void setPressed(boolean pressed) {
+ if (mArmed != pressed) {
+ mArmed = pressed;
+ updateSprites();
+ }
+ }
+
+ public void bringToFront() {
+ int i;
+ for (i = 0; i < mNormalSprites.size(); i++) {
+ if (mNormalSprites.get(i).enabled) {
+ mRenderer.bringToFront(mNormalSprites.get(i));
+ }
+ }
+ for (i = 0; i < mHighlightSprites.size(); i++) {
+ if (mHighlightSprites.get(i).enabled) {
+ mRenderer.bringToFront(mHighlightSprites.get(i));
+ }
+ }
+ }
+
+ private boolean isInButton(float x, float y) {
+ if (!mVisible) {
+ return false;
+ }
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ return dx < 0.5f * mWidth && dy <= 0.5f * mHeight;
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, float x, float y) {
+ super.onPointerDown(pointerId, x, y);
+ if (isInButton(x, y)) {
+ mArmed = true;
+ updateSprites();
+ }
+ }
+
+ @Override
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ super.onPointerMove(pointerId, x, y, deltaX, deltaY);
+ if (!isInButton(x, y)) {
+ mArmed = false;
+ updateSprites();
+ }
+ }
+
+ @Override
+ public void onPointerUp(int pointerId, float x, float y) {
+ super.onPointerUp(pointerId, x, y);
+ if (mArmed && isInButton(x, y)) {
+ mArmed = false;
+ updateSprites();
+ if (mListener != null) {
+ mListener.onWidgetTriggered(mTriggerMessage);
+ }
+ }
+ }
+
+ @Override
+ public void dispose() {
+ for (Renderer.Sprite sp : mNormalSprites) {
+ mRenderer.deleteSprite(sp);
+
+ // remove it from the other collection too, in case it's shared between them:
+ mHighlightSprites.remove(sp);
+ }
+ mNormalSprites.clear();
+ for (Renderer.Sprite sp : mHighlightSprites) {
+ mRenderer.deleteSprite(sp);
+ }
+ mRenderer = null;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/SimpleUI.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/SimpleUI.java
new file mode 100644
index 000000000..accacbb6c
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/SimpleUI.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine.ui;
+
+import com.google.android.apps.santatracker.games.simpleengine.Renderer;
+
+import java.util.ArrayList;
+
+public class SimpleUI {
+
+ Renderer mRenderer;
+ ArrayList mWidgets = new ArrayList();
+
+ public SimpleUI(Renderer renderer) {
+ mRenderer = renderer;
+ }
+
+ public void add(Widget widget) {
+ if (!mWidgets.contains(widget)) {
+ mWidgets.add(widget);
+ }
+ }
+
+ public void doFrame(float deltaT) {
+ for (Widget w : mWidgets) {
+ w.doFrame(deltaT);
+ }
+ }
+
+ public void onPointerDown(int pointerId, float x, float y) {
+ for (Widget w : mWidgets) {
+ w.onPointerDown(pointerId, x, y);
+ }
+ }
+
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ for (Widget w : mWidgets) {
+ w.onPointerMove(pointerId, x, y, deltaX, deltaY);
+ }
+ }
+
+ public void onPointerUp(int pointerId, float x, float y) {
+ for (Widget w : mWidgets) {
+ w.onPointerUp(pointerId, x, y);
+ }
+ }
+
+ public void dispose() {
+ for (Widget w : mWidgets) {
+ w.dispose();
+ }
+ mWidgets.clear();
+ mRenderer = null;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Widget.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Widget.java
new file mode 100644
index 000000000..fdaa19ed4
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/games/simpleengine/ui/Widget.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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.games.simpleengine.ui;
+
+public class Widget {
+
+ public interface WidgetTriggerListener {
+
+ public void onWidgetTriggered(int message);
+ }
+
+ public void doFrame(float deltaT) {
+ }
+
+ public void onPointerDown(int pointerId, float x, float y) {
+ }
+
+ public void onPointerMove(int pointerId, float x, float y, float deltaX, float deltaY) {
+ }
+
+ public void onPointerUp(int pointerId, float x, float y) {
+ }
+
+ public void dispose() {
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/AbstractLaunch.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/AbstractLaunch.java
new file mode 100644
index 000000000..ba07df27a
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/AbstractLaunch.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 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.UiModeManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.view.View;
+import android.widget.Toast;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+/**
+ * Partial implementation of a launch from the main village into a specific game or activity. All
+ * launches are visually illustrated with a marker and must be defined with color, a badge and some
+ * state that defines whether the marker is locked or available.
+ */
+public abstract class AbstractLaunch implements View.OnClickListener,
+ View.OnLongClickListener, VoiceAction.VoiceActionHandler {
+
+ public static final int STATE_LOCKED = 0;
+ public static final int STATE_READY = 1;
+ public static final int STATE_DISABLED = 2;
+ public static final int STATE_FINISHED = 3;
+ public static final int STATE_HIDDEN = 4;
+
+ private static final String TAG = "AbstractLaunch";
+
+ protected int mState = STATE_DISABLED;
+ protected SantaContext mContext;
+ protected String mContentDescription;
+ protected int mCardDrawable;
+ protected View mClickTarget;
+ private LauncherDataChangedCallback mLauncherCallback;
+ private View mLockedView;
+
+ /**
+ * Constructs a new launch (marker).
+ *
+ * @param context The application (Santa) context
+ * @param contentDescriptionId The name of the marker, used for accessibility
+ * @param cardDrawable The resource ID of the card to draw
+ */
+ public AbstractLaunch(SantaContext context, LauncherDataChangedCallback adapter, int contentDescriptionId,
+ int cardDrawable) {
+ initialise(context, adapter, contentDescriptionId, cardDrawable);
+ }
+
+ protected void initialise(SantaContext context, LauncherDataChangedCallback adapter, int contentDescriptionId,
+ int cardDrawable) {
+ setContext(context);
+ mLauncherCallback = adapter;
+ mState = STATE_DISABLED;
+ mContentDescription = mContext.getResources().getString(contentDescriptionId);
+ mCardDrawable = cardDrawable;
+ }
+
+ /** Sets the SantaContext. */
+ public void setContext(SantaContext context) {
+ mContext = context;
+ mState = STATE_DISABLED;
+ }
+
+ public void attachToView(View view) {
+ mClickTarget = view;
+ mClickTarget.setOnClickListener(this);
+ }
+
+ public int getCardResource() {
+ return mCardDrawable;
+ }
+
+ public View getClickTarget() {
+ return mClickTarget;
+ }
+
+ public String getContentDescription() {
+ return mContentDescription;
+ }
+
+ /** Attaches events to the marker, updating the image based on the current state. */
+ public void applyState() {
+ if (mLockedView != null) {
+ if (mState == STATE_DISABLED || mState == STATE_LOCKED || mState == STATE_FINISHED) {
+ mLockedView.setVisibility(View.VISIBLE);
+ } else {
+ mLockedView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ public void setLockedView(View lockedView) {
+ mLockedView = lockedView;
+ }
+
+ /**
+ * Updates the state of the image (e.g. locked).
+ *
+ * @param state One of {@code STATE_LOCKED, STATE_READY, STATE_DISABLED, STATE_FINISHED,
+ * STATE_HIDDEN}
+ */
+ public void setState(int state) {
+ if (mState != state) {
+ mState = state;
+ applyState();
+ mLauncherCallback.refreshData();
+ }
+ }
+
+ /**
+ * Display a {@link android.widget.Toast} with a given string resource message.
+ */
+ protected void notify(Context context, int stringId) {
+ Toast.makeText(context, context.getResources().getText(stringId), Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ /**
+ * Display a {@link android.widget.Toast} with a given string resource message with additional
+ * string parameters.
+ *
+ * @see android.content.res.Resources#getString(int, Object...)
+ */
+ protected void notify(Context context, int stringId, Object... args) {
+ Toast.makeText(context, context.getResources().getString(stringId, args),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ /** Retrieves the current marker state. */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Convenience method used for launching games.
+ * Simulate onClick event if the intent received is of the type specified by
+ * actionName and the extra value matches the content description of the current
+ * instance.
+ *
+ * @param intent the intent to examine
+ * @param actionName the action name, for example VoiceAction.ACTION_PLAY_GAME
+ * @param extraName the extra name, for example VoiceAction.ACTION_PLAY_GAME_EXTRA
+ * @return true if matched and OnClick was invoked
+ */
+ protected boolean clickIfMatchesDescription(Intent intent, String actionName,
+ String extraName) {
+ String action = intent.getAction();
+ if (actionName.equals(action)) {
+ String description = intent.getStringExtra(extraName);
+ if (mContentDescription.equalsIgnoreCase(description)) {
+ SantaLog.d(TAG,
+ String.format("Voice command: [%s] [%s]", actionName,
+ description));
+
+ onClick(mClickTarget);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getVerb() {
+ return mContext.getResources().getString(R.string.play);
+ }
+
+ /**
+ * Handles a voice action. Override if the implementation wishes to handle voice actions and
+ * return true if it has been or will be handled.
+ *
+ * @param intent Google Now Actions intent.
+ * @return true if the action was handled or will be handled
+ */
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return false;
+ }
+
+ /**
+ * Determines if this marker is a game. Default is true, so override is only necessary if
+ * implementation is not a game.
+ *
+ * @return true if this launcher will launch a game, false otherwise.
+ */
+ public boolean isGame() {
+ return true;
+ }
+
+ /** Checck whether the app is running on Tv or not. */
+ protected boolean isTV() {
+
+ final Context context = mContext.getApplicationContext();
+ UiModeManager manager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
+
+ return manager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardAdapter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardAdapter.java
new file mode 100644
index 000000000..1b30a0a28
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardAdapter.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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.graphics.Typeface;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.google.android.apps.santatracker.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CardAdapter extends RecyclerView.Adapter
+ implements LauncherDataChangedCallback {
+
+ public static final int SANTA = 0;
+ public static final int VIDEO01 = 1;
+ public static final int GUMBALL = 2;
+ public static final int MEMORY = 3;
+ public static final int JETPACK = 4;
+ public static final int VIDEO15 = 5;
+ public static final int ROCKET = 6;
+ public static final int DANCER = 7;
+ public static final int SNOWDOWN = 8;
+ public static final int VIDEO23 = 9;
+ public static final int NUM_PINS = 10;
+
+ private final Typeface mFont;
+
+ private AbstractLaunch[] mAllLaunchers = new AbstractLaunch[NUM_PINS];
+ private AbstractLaunch[] mLaunchers = new AbstractLaunch[NUM_PINS];
+
+ public CardAdapter(SantaContext santaContext) {
+ mAllLaunchers[SANTA] = new LaunchSanta(santaContext, this);
+ mAllLaunchers[VIDEO01] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_santas_back, 1);
+ mAllLaunchers[GUMBALL] = new LaunchGumball(santaContext, this);
+ mAllLaunchers[MEMORY] = new LaunchMemory(santaContext, this);
+ mAllLaunchers[JETPACK] = new LaunchJetpack(santaContext, this);
+ mAllLaunchers[VIDEO15] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_office_prank, 1);
+ mAllLaunchers[ROCKET] = new LaunchRocket(santaContext, this);
+ mAllLaunchers[DANCER] = new LaunchDancer(santaContext, this);
+ mAllLaunchers[SNOWDOWN] = new LaunchSnowdown(santaContext, this);
+ mAllLaunchers[VIDEO23] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_elf_car, 23);
+
+ updateMarkerVisibility();
+
+ mFont = Typeface.createFromAsset(santaContext.getActivityContext().getAssets(),
+ "Lobster-Regular.otf");
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ View v = inflater.inflate(R.layout.layout_village_card, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ holder.setLauncher(mLaunchers[position], mFont);
+ holder.launcher.setLockedView(holder.lockedView);
+ holder.launcher.applyState();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mLaunchers.length;
+ }
+
+ @Override
+ public void refreshData() {
+ updateMarkerVisibility();
+ notifyDataSetChanged();
+ }
+
+ public void updateMarkerVisibility() {
+ List launchers = new ArrayList<>(mAllLaunchers.length);
+ for (int i = 0; i < mAllLaunchers.length; i++) {
+ if (mAllLaunchers[i].getState() != AbstractLaunch.STATE_HIDDEN) {
+ launchers.add(mAllLaunchers[i]);
+ }
+ }
+ mLaunchers = launchers.toArray(new AbstractLaunch[launchers.size()]);
+ }
+
+ public AbstractLaunch[] getLaunchers() {
+ return mAllLaunchers;
+ }
+
+ public AbstractLaunch getLauncher(int i) {
+ return mAllLaunchers[i];
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ public AbstractLaunch launcher;
+ public ImageView backgroundImageView;
+ public TextView nameView;
+ public TextView verbView;
+ public View lockedView;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+
+ backgroundImageView = (ImageView) itemView.findViewById(R.id.card_background_image);
+ nameView = (TextView) itemView.findViewById(R.id.card_name_text);
+ verbView = (TextView) itemView.findViewById(R.id.card_verb);
+ lockedView = itemView.findViewById(R.id.card_disabled);
+ }
+
+ public void setLauncher(AbstractLaunch launcher, Typeface tf) {
+ this.launcher = launcher;
+
+ // Loading all of these beautiful images at full res is laggy without using
+ // Glide, however this makes it asynchronous. We should consider either compromising
+ // on image resolution or doing some sort of nifty placeholder.
+ Glide.with(itemView.getContext())
+ .fromResource()
+ .centerCrop()
+ .load(launcher.getCardResource())
+ .into(backgroundImageView);
+
+ nameView.setText(launcher.getContentDescription());
+ verbView.setText(launcher.getVerb());
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
+ verbView.setTypeface(tf);
+ } else {
+ verbView.setTypeface(tf, Typeface.ITALIC);
+ }
+ itemView.setContentDescription(launcher.getContentDescription());
+
+ launcher.attachToView(this.itemView);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardLayoutManager.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardLayoutManager.java
new file mode 100644
index 000000000..094f2e5e5
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/CardLayoutManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+public class CardLayoutManager extends GridLayoutManager {
+
+ public static final String TAG = "CardLayoutManager";
+
+ public CardLayoutManager(Context context, int numColumns) {
+ super(context, numColumns);
+ }
+
+ @Override
+ public int scrollVerticallyBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ float multiplier = 1.0f;
+ float base = (float) super.scrollVerticallyBy(delta, recycler, state);
+
+ // TODO(samstern): slow down at card borders
+ return ((int) (multiplier * base));
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchCountdown.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchCountdown.java
new file mode 100644
index 000000000..beaf3929c
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchCountdown.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.os.Build;
+import android.os.CountDownTimer;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+
+import java.lang.ref.WeakReference;
+import java.text.DecimalFormat;
+import java.util.GregorianCalendar;
+
+public class LaunchCountdown {
+
+ /**
+ * Listener for LaunchCountdown and convenience method to draw CountDown UI itself.
+ */
+ public interface LaunchCountdownContext{
+ void onCountdownFinished();
+ View getCountdownView();
+ Context getActivityContext();
+ Context getApplicationContext();
+ }
+
+ private CountDownTimer mTimer;
+
+ private TextView mTvDays;
+ private TextView mTvDays2;
+ private TextView mTvHours;
+ private TextView mTvHours2;
+ private TextView mTvMinutes;
+ private TextView mTvMinutes2;
+ private TextView mTvSeconds;
+ private TextView mTvSeconds2;
+
+ boolean mDaysPrimary = true;
+ Animation mDaysIn, mDaysOut;
+ boolean mHoursPrimary = true;
+ Animation mHoursIn, mHoursOut;
+ boolean mMinutesPrimary = true;
+ Animation mMinutesIn, mMinutesOut;
+ boolean mSecondsPrimary = true;
+ Animation mSecondsIn, mSecondsOut;
+ long mCountdownTime = -1;
+
+ private WeakReference mLaunchContextRef;
+
+ public LaunchCountdown(LaunchCountdownContext context) {
+ mLaunchContextRef = new WeakReference<>(context);
+ final View countdownView = context.getCountdownView();
+ mTvDays = (TextView) countdownView.findViewById(R.id.countdown_days_1);
+ mTvDays2 = (TextView) countdownView.findViewById(R.id.countdown_days_2);
+ mTvHours = (TextView) countdownView.findViewById(R.id.countdown_hours_1);
+ mTvHours2 = (TextView) countdownView.findViewById(R.id.countdown_hours_2);
+ mTvMinutes = (TextView) countdownView.findViewById(R.id.countdown_minutes_1);
+ mTvMinutes2 = (TextView) countdownView.findViewById(R.id.countdown_minutes_2);
+ mTvSeconds = (TextView) countdownView.findViewById(R.id.countdown_seconds_1);
+ mTvSeconds2 = (TextView) countdownView.findViewById(R.id.countdown_seconds_2);
+ }
+
+ /**
+ * Starts the countdown timer.
+ */
+ public void startTimer(long timer) {
+ final LaunchCountdownContext launchContext = mLaunchContextRef.get();
+ if (timer < 0 || Math.abs(timer - mCountdownTime) < 1000 || launchContext == null) {
+ return;
+ }
+
+ mCountdownTime = timer;
+ // cancel timer if already running
+ cancel();
+
+ final Context context = launchContext.getApplicationContext();
+
+ mDaysIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_bottom);
+ mDaysOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_top);
+ mHoursIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_bottom);
+ mHoursOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_top);
+ mMinutesIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_bottom);
+ mMinutesOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_top);
+ mSecondsIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_bottom);
+ mSecondsOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_top);
+
+ mTimer = new CountDownTimer(mCountdownTime, 1000) {
+ private DecimalFormat mDF = new DecimalFormat("00");
+ private boolean initialRound = true;
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+
+ int iDays = (int) Math.floor(millisUntilFinished / (24 * 60 * 60 * 1000));
+ int iHours = (int) Math.floor(millisUntilFinished / (60 * 60 * 1000) % 24);
+ int iMinutes = (int) Math.floor(millisUntilFinished / (60 * 1000) % 60);
+ int iSeconds = (int) Math.floor(millisUntilFinished / (1000) % 60);
+
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTimeInMillis(millisUntilFinished);
+
+ String days = mDF.format(iDays);
+ if (animateValue(days, mTvDays, mTvDays2, mDaysIn, mDaysOut, mDaysPrimary,
+ initialRound)) {
+ mDaysPrimary = !mDaysPrimary;
+ }
+
+ String hours = mDF.format(iHours);
+ if (animateValue(hours, mTvHours, mTvHours2, mHoursIn, mHoursOut, mHoursPrimary,
+ initialRound)) {
+ mHoursPrimary = !mHoursPrimary;
+ }
+
+ String minutes = mDF.format(iMinutes);
+ if (animateValue(minutes, mTvMinutes, mTvMinutes2, mMinutesIn, mMinutesOut,
+ mMinutesPrimary, initialRound)) {
+ mMinutesPrimary = !mMinutesPrimary;
+ }
+
+ String seconds = mDF.format(iSeconds);
+ if (animateValue(seconds, mTvSeconds, mTvSeconds2, mSecondsIn, mSecondsOut,
+ mSecondsPrimary, initialRound)) {
+ mSecondsPrimary = !mSecondsPrimary;
+ }
+
+ if (initialRound) {
+ initialRound = false;
+ }
+ }
+
+ // Returns true if animation was triggered.
+ private boolean animateValue(String value, TextView viewOne, TextView viewTwo,
+ Animation animIn, Animation animOut, boolean usePrimary, boolean initialRound) {
+ if (viewOne == null || viewTwo == null) {
+ return false;
+ }
+ boolean rc = false;
+
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ if (initialRound) {
+ viewOne.setText(value);
+ viewOne.setContentDescription(viewOne.getText());
+ viewTwo.setText(value);
+ viewTwo.setContentDescription(viewTwo.getText());
+ viewOne.startAnimation(animIn);
+ viewTwo.startAnimation(animOut);
+ //noinspection ConstantConditions
+ return rc;
+ }
+
+ String one = viewOne.getText().toString();
+ String two = viewTwo.getText().toString();
+ if (usePrimary) {
+ if (value.compareTo(two) != 0) {
+ viewOne.setText(value);
+ viewOne.setContentDescription(viewOne.getText());
+ viewOne.clearAnimation();
+ viewOne.startAnimation(animIn);
+
+ viewTwo.clearAnimation();
+ viewTwo.startAnimation(animOut);
+ rc = true;
+ }
+ } else {
+ if (value.compareTo(one) != 0) {
+ viewTwo.setText(value);
+ viewTwo.setContentDescription(viewTwo.getText());
+ viewTwo.clearAnimation();
+ viewTwo.startAnimation(animIn);
+
+ viewOne.clearAnimation();
+ viewOne.startAnimation(animOut);
+ rc = true;
+ }
+ }
+ } else {
+ // Skip animations on ICS and below.
+ viewOne.setText(value);
+ viewOne.setContentDescription(viewOne.getText());
+ if (initialRound) {
+ viewOne.setVisibility(View.VISIBLE);
+ }
+ }
+ return rc;
+ }
+
+ @Override
+ public void onFinish() {
+ final LaunchCountdownContext launchContext = mLaunchContextRef.get();
+ if (launchContext != null) {
+ launchContext.onCountdownFinished();
+ }
+ }
+ };
+ mTimer.start();
+ }
+
+ public void cancel() {
+ if (mTimer != null) {
+ mTimer.cancel();
+ }
+ mTimer = null;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchDancer.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchDancer.java
new file mode 100644
index 000000000..82981a2e5
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchDancer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.dasherdancer.DasherDancerActivity;
+
+/** Launch the Dasher Dancer game. */
+public class LaunchDancer extends AbstractLaunch {
+
+ public LaunchDancer(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.dancer, R.drawable.android_game_cards_jingle_elves);
+ }
+
+ static public int getId() {
+ return R.string.dancer;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), DasherDancerActivity.class), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.dancer_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.dancer_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch(mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.dancer);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.dancer_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(),R.string.dancer_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchGumball.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchGumball.java
new file mode 100644
index 000000000..e62269fe7
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchGumball.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.GumballActivity;
+
+/**
+ * Launch the Gumball Tilt game.
+ */
+public class LaunchGumball extends AbstractLaunch {
+
+ public LaunchGumball(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.gumball, R.drawable.android_game_cards_gumball_tilt);
+ }
+
+ static public int getId() {
+ return R.string.gumball;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), GumballActivity.class), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.gumball_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.gumball_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.gumball);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.gumball_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.gumball_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchJetpack.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchJetpack.java
new file mode 100644
index 000000000..56ac26c5b
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchJetpack.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.jetpack.JetpackActivity;
+
+/**
+ * Launch the Elf Jetpack game.
+ */
+public class LaunchJetpack extends AbstractLaunch {
+
+ public LaunchJetpack(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.elf_jetpack, R.drawable.android_game_cards_elf_jetpack);
+ }
+
+ static public int getId() {
+ return R.string.elf_jetpack;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), JetpackActivity.class), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.elf_jetpack_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.elf_jetpack_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.elf_jetpack);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.elf_jetpack_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.elf_jetpack_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchMemory.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchMemory.java
new file mode 100644
index 000000000..bc9286801
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchMemory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.games.MemoryActivity;
+
+/**
+ * Launch the Memory Match game.
+ */
+public class LaunchMemory extends AbstractLaunch {
+
+ public LaunchMemory(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.memory, R.drawable.android_game_cards_memory_match);
+ }
+
+ static public int getId() {
+ return R.string.memory;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), MemoryActivity.class), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.memory_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.memory_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.memory);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.memory_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.memory_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchRocket.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchRocket.java
new file mode 100644
index 000000000..4fc66274d
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchRocket.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.rocketsleigh.RocketSleighActivity;
+
+/** Launch the Rocket Sleigh game. */
+public class LaunchRocket extends AbstractLaunch {
+
+ public LaunchRocket(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.rocket, R.drawable.android_game_cards_haywire_ride);
+ }
+
+ static public int getId() {
+ return R.string.rocket;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), RocketSleighActivity.class), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.rocket_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.rocket_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch(mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.rocket);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.rocket_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(),R.string.rocket_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSanta.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSanta.java
new file mode 100644
index 000000000..9e2ce63c5
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSanta.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 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;
+ }
+}
+
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSnowdown.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSnowdown.java
new file mode 100644
index 000000000..95c0c259d
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchSnowdown.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.fpl.pie_noon.FPLActivity;
+import com.google.fpl.pie_noon.FPLTvActivity;
+
+/**
+ * Launches Snowdown (FKA Pie Noon).
+ */
+public class LaunchSnowdown extends AbstractLaunch {
+
+ private final Class mLaunchActivityClass;
+
+ public LaunchSnowdown(SantaContext context, LauncherDataChangedCallback adapter) {
+ super(context, adapter, R.string.snowdown, R.drawable.android_game_cards_snowdown);
+
+
+ if (isTV()) {
+ mLaunchActivityClass = FPLTvActivity.class;
+ } else {
+ mLaunchActivityClass = FPLActivity.class;
+ }
+ }
+
+ static public int getId() {
+ return R.string.snowdown;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ mContext.launchActivityDelayed(
+ new Intent(mContext.getActivityContext(), mLaunchActivityClass), v);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.snowdown_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.snowdown_locked);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.snowdown);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.snowdown_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.snowdown_locked);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ return clickIfMatchesDescription(intent, VoiceAction.ACTION_PLAY_GAME,
+ VoiceAction.ACTION_PLAY_GAME_EXTRA);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchVideo.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchVideo.java
new file mode 100644
index 000000000..aedca8213
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LaunchVideo.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.util.Intents;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/** Launch a YouTube video. */
+public class LaunchVideo extends AbstractLaunch {
+
+ public static final String HIDDEN_VIDEO = "_disabled";
+
+ private final int mCardDrawableId;
+ private final int mUnlockDate;
+ private String mVideoId;
+ private FirebaseAnalytics mMeasurement;
+
+ /**
+ * Constructs a video-launching marker.
+ * @param context The SantaContext
+ * @param adapter
+ * @param cardDrawableId The card drawable to use in the village
+ * @param unlockDate The day in December to unlock this video (e.g. 05 for December 5)
+ */
+ public LaunchVideo(SantaContext context, LauncherDataChangedCallback adapter, int cardDrawableId,
+ int unlockDate) {
+
+ super(context, adapter, R.string.video, cardDrawableId);
+ mCardDrawableId = cardDrawableId;
+ mUnlockDate = unlockDate;
+ mMeasurement = FirebaseAnalytics.getInstance(context.getApplicationContext());
+ }
+
+ static public int getId() {
+ return R.string.rocket;
+ }
+
+ @Override
+ public String getVerb() {
+ return mContext.getResources().getString(R.string.watch);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ Intent intent = Intents.getYoutubeIntent(mContext.getApplicationContext(), mVideoId);
+ mContext.launchActivityDelayed(intent, v);
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ mContext.getResources().getString(R.string.analytics_event_category_launch),
+ mContext.getResources().getString(R.string.analytics_tracker_action_video),
+ mVideoId);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.video_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.video_disabled, mUnlockDate);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (mState) {
+ case STATE_READY:
+ case STATE_FINISHED:
+ notify(mContext.getApplicationContext(), R.string.video);
+ break;
+ case STATE_DISABLED:
+ notify(mContext.getApplicationContext(), R.string.video_disabled);
+ break;
+ case STATE_LOCKED:
+ default:
+ notify(mContext.getApplicationContext(), R.string.video_locked, mUnlockDate);
+ break;
+ }
+ return true;
+ }
+
+ public void setVideo(String videoId, long unlockTime) {
+ if (HIDDEN_VIDEO.equals(videoId)) {
+ // video explicitly disabled
+ mVideoId = null;
+ setState(STATE_HIDDEN);
+ } else if (videoId != null && !videoId.isEmpty() && !videoId.equals("null")) {
+ // JSONObject.getString will coerce a null value into "null"
+ // valid-looking video ID - unlock regardless of time
+ mVideoId = videoId;
+ setState(STATE_READY);
+ } else if (SantaPreferences.getCurrentTime() < unlockTime) {
+ // video not-yet unlocked
+ mVideoId = null;
+ setState(STATE_LOCKED);
+ } else {
+ // video ID null or not present and video should be unlocked
+ mVideoId = null;
+ setState(STATE_DISABLED);
+ }
+ }
+
+ @Override
+ public boolean isGame() {
+ return false;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LauncherDataChangedCallback.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LauncherDataChangedCallback.java
new file mode 100644
index 000000000..efe1027cc
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/LauncherDataChangedCallback.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Hooks for updating a launcher when it has a change in data, e.g. when a card becomes disabled.
+ */
+public interface LauncherDataChangedCallback {
+ void refreshData();
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaCollapsingToolbarLayout.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaCollapsingToolbarLayout.java
new file mode 100644
index 000000000..cf5b64c01
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaCollapsingToolbarLayout.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 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.animation.FloatEvaluator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class SantaCollapsingToolbarLayout extends CollapsingToolbarLayout {
+
+ private static final String TAG = "SantaCollapsing";
+
+ private static final float TRANSPARENT = 0.0f;
+ private static final float OPAQUE = 1.0f;
+
+ private boolean mScrimShown = false;
+
+ private View mToolbarContentView;
+ private View mOverlayView;
+
+ private ObjectAnimator mToolbarAnimator;
+ private ObjectAnimator mOverlayAnimator;
+
+ public SantaCollapsingToolbarLayout(Context context) {
+ super(context);
+ }
+
+ public SantaCollapsingToolbarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs, 0);
+ }
+
+ public SantaCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void setScrimsShown(boolean shown) {
+ super.setScrimsShown(shown);
+
+ // No change
+ if (shown == mScrimShown) {
+ return;
+ }
+
+ if (shown) {
+ Log.d(TAG, "setScrimShown:showing");
+ animateToolbar(TRANSPARENT, OPAQUE);
+ } else {
+ Log.d(TAG, "setScrimShown:hiding");
+ animateToolbar(OPAQUE, TRANSPARENT);
+ }
+
+ mScrimShown = shown;
+ }
+
+ private void animateToolbar(float fromAlpha, float toAlpha) {
+ if (mToolbarAnimator == null) {
+ mToolbarAnimator = ObjectAnimator.ofObject(mToolbarContentView, "alpha", new FloatEvaluator(),
+ fromAlpha, toAlpha)
+ .setDuration(600);
+ }
+
+ if (mOverlayAnimator == null) {
+ mOverlayAnimator = ObjectAnimator.ofObject(mOverlayView, "alpha", new FloatEvaluator(),
+ fromAlpha, toAlpha)
+ .setDuration(600);
+ }
+
+ startAnimator(mOverlayAnimator, fromAlpha, toAlpha);
+ startAnimator(mToolbarAnimator, fromAlpha, toAlpha);
+ }
+
+ private void startAnimator(ObjectAnimator animator, float from, float to) {
+ if (animator.isRunning()) {
+ animator.cancel();
+ }
+
+ animator.setFloatValues(from, to);
+ animator.start();
+ }
+
+
+ public void setToolbarContentView(View toolbarContentView) {
+ mToolbarContentView = toolbarContentView;
+ }
+
+ public void setOverlayView(View overlayView) {
+ mOverlayView = overlayView;
+ }
+
+ public void setOverlayColor(int colorResource) {
+ mOverlayView.setBackgroundResource(colorResource);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaContext.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaContext.java
new file mode 100644
index 000000000..92eec6373
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SantaContext.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.view.View;
+
+/**
+ * Interface for Context holder and convenience methods for AbstractLaunch objects.
+ */
+public interface SantaContext {
+
+ Context getActivityContext();
+ Context getApplicationContext();
+ Resources getResources();
+ void launchActivity(Intent intent);
+ void launchActivityDelayed(Intent intent, View v);
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SquareFrameLayout.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SquareFrameLayout.java
new file mode 100644
index 000000000..1209858dc
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/SquareFrameLayout.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * FrameLayout that is always square, useful for launcher cards.
+ */
+public class SquareFrameLayout extends FrameLayout {
+ public SquareFrameLayout(Context context) {
+ super(context);
+ }
+
+ public SquareFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SquareFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StartupActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StartupActivity.java
new file mode 100644
index 000000000..71b9f2168
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StartupActivity.java
@@ -0,0 +1,1348 @@
+/*
+ * Copyright (C) 2015 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.Manifest;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.load.DecodeFormat;
+import com.google.android.apps.santatracker.AudioPlayer;
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.SantaApplication;
+import com.google.android.apps.santatracker.SantaNotificationBuilder;
+import com.google.android.apps.santatracker.cast.NotificationDataCastManager;
+import com.google.android.apps.santatracker.common.NotificationConstants;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.games.PlayGamesFragment;
+import com.google.android.apps.santatracker.games.SignInListener;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+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.MeasurementManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.apps.santatracker.village.Village;
+import com.google.android.apps.santatracker.village.VillageView;
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.games.Games;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Launch activity for the app. Handles loading of the village, the state of the markers (based on
+ * the date/time) and incoming voice intents.
+ */
+public class StartupActivity extends AppCompatActivity implements
+ View.OnClickListener, Village.VillageListener,
+ VoiceAction.VoiceActionHandler, SignInListener, SantaContext, LaunchCountdown.LaunchCountdownContext {
+
+ protected static final String TAG = "SantaStart";
+ private static final String VILLAGE_TAG = "VillageFragment";
+ private static final String INTENT_HANDLED = "intent_handled";
+
+ private PlayGamesFragment mGamesFragment;
+ private AppInvitesFragment mInvitesFragment;
+ private AudioPlayer mAudioPlayer;
+
+ private boolean mResumed = false;
+ private boolean mSignedIn = false;
+
+ private Village mVillage;
+ private VillageView mVillageView;
+ private ImageView mVillageBackdrop;
+ private View mLaunchButton;
+ private SantaCollapsingToolbarLayout mSantaCollapsing;
+ private View mCountdownView;
+ private View mWavingSanta;
+ private ImageView mWavingSantaArm;
+ private Animation mWavingAnim;
+ private View mOrnament;
+
+ // private MarkerManager mMarkerManager;
+ private CardAdapter mCardAdapter;
+ private CardLayoutManager mCardLayoutManager;
+ private StickyScrollListener mScrollListener;
+ private RecyclerView mMarkers;
+
+ private TextView mStatusText;
+ private LaunchCountdown mCountdown;
+
+ // Load these values from resources when an instance of this activity is initialised.
+ private static long OFFLINE_SANTA_DEPARTURE;
+ private static long OFFLINE_SANTA_FINALARRIVAL;
+ private static long UNLOCK_GUMBALL;
+ private static long UNLOCK_JETPACK;
+ private static long UNLOCK_MEMORY;
+ private static long UNLOCK_ROCKET;
+ private static long UNLOCK_DANCER;
+ private static long UNLOCK_SNOWDOWN;
+ private static long UNLOCK_VIDEO_1;
+ private static long UNLOCK_VIDEO_15;
+ private static long UNLOCK_VIDEO_23;
+
+ // Server controlled flags
+ private long mOffset = 0;
+ private boolean mFlagSwitchOff = false;
+ private boolean mFlagDisableCast = false;
+ private boolean mFlagDisableGumball = false;
+ private boolean mFlagDisableJetpack = false;
+ private boolean mFlagDisableMemory = false;
+ private boolean mFlagDisableRocket = false;
+ private boolean mFlagDisableDancer = false;
+ private boolean mFlagDisableSnowdown = false;
+
+ private String[] mVideoList = new String[]{null, null, null};
+
+
+ private boolean mHaveGooglePlayServices = false;
+ private long mFirstDeparture;
+ private long mFinalArrival;
+
+ private MenuItem mMenuItemLegal;
+
+ // Handler for scheduled UI updates
+ private Handler mHandler = new Handler();
+
+ // Waiting for data from the API (no data or data is outdated)
+ private boolean mWaitingForApi = true;
+
+ // Launching a child activity (another launch request should be blocked)
+ private boolean mLaunchingChild = false;
+
+ // Service integration
+ private Messenger mService = null;
+
+ private boolean mIsBound = false;
+ private final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+ // request code for games Activities
+ private final int RC_STARTUP = 1111;
+ private final int RC_GAMES = 9999;
+
+ // Permission request codes
+ private final int RC_DEBUG_PERMS = 1;
+
+ private FirebaseAnalytics mMeasurement;
+
+ private NotificationDataCastManager mCastManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestPermissionsIfDebugModeEnabled();
+
+ // Glide's pretty aggressive at caching images, so get the 8888 preference in early.
+ if (!Glide.isSetup()) {
+ Glide.setup(new GlideBuilder(getActivityContext())
+ .setDecodeFormat(DecodeFormat.PREFER_ARGB_8888));
+ }
+
+ // TODO: rename temp 'layout_startup_2015' layout when its implementation is completed.
+ setContentView(R.layout.layout_startup_2015);
+
+ loadResourceFields(getResources());
+
+ mCountdown = new LaunchCountdown(this);
+ mCountdownView = findViewById(R.id.countdown_container);
+ mAudioPlayer = new AudioPlayer(getApplicationContext());
+
+ Toolbar toolBar = (Toolbar) findViewById(R.id.toolbar);
+ if (toolBar != null) {
+ setSupportActionBar(toolBar);
+ }
+
+ // Set up collapsing
+ mSantaCollapsing = (SantaCollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
+ mSantaCollapsing.setToolbarContentView(findViewById(R.id.toolbar_content));
+ mSantaCollapsing.setOverlayView(findViewById(R.id.view_color_overlay));
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setHomeButtonEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ }
+
+ mStatusText = (TextView) findViewById(R.id.statusText);
+
+ mVillageView = (VillageView) findViewById(R.id.villageView);
+ mVillage = (Village) getSupportFragmentManager().findFragmentByTag(VILLAGE_TAG);
+ if (mVillage == null) {
+ mVillage = new Village();
+ getSupportFragmentManager().beginTransaction().add(mVillage, VILLAGE_TAG).commit();
+ }
+ mVillageBackdrop = (ImageView) findViewById(R.id.villageBackground);
+ mLaunchButton = findViewById(R.id.launch_button);
+ mLaunchButton.setOnClickListener(this);
+ mOrnament = findViewById(R.id.countdown_ornament);
+
+ mWavingSanta = findViewById(R.id.santa_waving);
+ mWavingSantaArm = (ImageView) findViewById(R.id.santa_arm);
+ mWavingSanta.setOnClickListener(this);
+
+ mMarkers = (RecyclerView) findViewById(R.id.markers);
+ initialiseViews();
+
+ mHaveGooglePlayServices = NotificationDataCastManager.checkGooglePlayServices(this);
+
+ // initialize our connection to Google Play Games
+ mGamesFragment = PlayGamesFragment.getInstance(this, this);
+
+ // App invites
+ mInvitesFragment = AppInvitesFragment.getInstance(this);
+
+ // set up click listeners for our buttons
+ findViewById(R.id.fab_achievement).setOnClickListener(this);
+ findViewById(R.id.fab_leaderboard).setOnClickListener(this);
+
+ // Initialize measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_village));
+
+ // [ANALYTICS SCREEN]: Village
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_village);
+ // set the initial states
+ resetLauncherStates();
+ // See if it was a voice action which triggered this activity and handle it
+ onNewIntent(getIntent());
+
+ if (mHaveGooglePlayServices) {
+ mCastManager = SantaApplication.getCastManager(this);
+ }
+ }
+
+ private void loadResourceFields(Resources res) {
+ final long ms = 1000L;
+ OFFLINE_SANTA_DEPARTURE = res.getInteger(R.integer.santa_takeoff) * ms;
+ OFFLINE_SANTA_FINALARRIVAL = res.getInteger(R.integer.santa_arrival) * ms;
+ mFinalArrival = OFFLINE_SANTA_FINALARRIVAL;
+ mFirstDeparture = OFFLINE_SANTA_DEPARTURE;
+
+ // Game unlock
+ UNLOCK_GUMBALL = res.getInteger(R.integer.unlock_gumball) * ms;
+ UNLOCK_JETPACK = res.getInteger(R.integer.unlock_jetpack) * ms;
+ UNLOCK_MEMORY = res.getInteger(R.integer.unlock_memory) * ms;
+ UNLOCK_ROCKET = res.getInteger(R.integer.unlock_rocket) * ms;
+ UNLOCK_DANCER = res.getInteger(R.integer.unlock_dancer) * ms;
+ UNLOCK_SNOWDOWN = res.getInteger(R.integer.unlock_snowdown) * ms;
+
+ // Video unlock
+ UNLOCK_VIDEO_1 = res.getInteger(R.integer.unlock_video1) * ms;
+ UNLOCK_VIDEO_15 = res.getInteger(R.integer.unlock_video15) * ms;
+ UNLOCK_VIDEO_23 = res.getInteger(R.integer.unlock_video23) * ms;
+ }
+
+ void initialiseViews() {
+ mVillageView.setVillage(mVillage);
+
+ // Initialize the RecyclerView
+ int numColumns = getResources().getInteger(R.integer.village_columns);
+ mCardLayoutManager = new CardLayoutManager(this, numColumns);
+ mCardAdapter = new CardAdapter(this);
+ mScrollListener = new StickyScrollListener(mCardLayoutManager, numColumns);
+
+ mMarkers.setAdapter(mCardAdapter);
+ mMarkers.setLayoutManager(mCardLayoutManager);
+ mMarkers.addOnScrollListener(mScrollListener);
+ }
+
+ private void requestPermissionsIfDebugModeEnabled() {
+ // If debug mode is enabled in debug_settings.xml, and we don't yet have storage perms, ask.
+ if (getResources().getBoolean(R.bool.prompt_for_sdcard_perms)
+ && ContextCompat.checkSelfPermission(this,
+ Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+ RC_DEBUG_PERMS);
+ }
+ }
+
+ // see http://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances/
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ handleVoiceActions();
+
+ Bundle extras = intent.getExtras();
+ if (extras != null
+ && extras.getString(NotificationConstants.KEY_NOTIFICATION_TYPE) != null) {
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_notification),
+ extras.getString(NotificationConstants.KEY_NOTIFICATION_TYPE));
+
+ // [ANALYTICS EVENT]: Launch Notification
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_notification,
+ extras.getString(NotificationConstants.KEY_NOTIFICATION_TYPE));
+ SantaLog.d(TAG, "launched from notification");
+ }
+ }
+
+ @Override
+ public boolean handleVoiceAction(Intent intent) {
+ if (VoiceAction.ACTION_PLAY_RANDOM_GAME.equals(intent.getAction())) {
+ Log.d(TAG, String.format("Voice command: [%s]", VoiceAction.ACTION_PLAY_RANDOM_GAME));
+ return startRandomGame();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Pick a game at random from the available games in STATE_READY state
+ * @return true if a game was launched
+ */
+ private boolean startRandomGame() {
+ // find out all the games that are ready to play
+ AbstractLaunch[] pins = mCardAdapter.getLaunchers();
+ List games = new ArrayList(pins.length);
+ for (AbstractLaunch pin : pins) {
+ if (pin.isGame()) {
+ if (pin.mState == AbstractLaunch.STATE_READY) {
+ games.add(pin);
+ }
+ }
+ }
+ // now pick one of the games from games and launch it
+ if (games.size() > 0) {
+ Random r = new Random();
+ int index = r.nextInt(games.size());
+ AbstractLaunch game = games.get(index);
+ Log.d(TAG, String.format("Picked a game at random [%s]",
+ game.mContentDescription));
+ // launch the game by simulating a click
+ game.onClick(game.getClickTarget());
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_voice),
+ game.mContentDescription);
+
+ // [ANALYTICS EVENT]: Launch Voice
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_voice,
+ game.mContentDescription);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void handleVoiceActions() {
+ Intent intent = getIntent();
+ if (VoiceAction.isVoiceAction(intent)) {
+ if (isAlreadyHandled(intent)) {
+ Log.d(TAG, String.format("Ignoring an already handled intent [%s]",
+ intent.getAction()));
+ return; // already processed
+ }
+ boolean handled;
+ // first check if *this* activity can handle the voice action
+ handled = handleVoiceAction(intent);
+ // next check all the pins
+ if (!handled) {
+ AbstractLaunch[] pins = mCardAdapter.getLaunchers();
+ // try sending the voice command to all launchers, the first one that handles it wins
+ for (AbstractLaunch l : pins) {
+ if (handled = l.handleVoiceAction(intent)) {
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_voice),
+ l.mContentDescription);
+
+ // [ANALYTICS EVENT]: Launch Voice
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_voice,
+ l.mContentDescription);
+ break;
+ }
+ }
+ }
+ if (!handled) {
+ Toast.makeText(this, getResources().getText(R.string.voice_command_unhandled),
+ Toast.LENGTH_SHORT)
+ .show();
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_voice),
+ getString(R.string.analytics_launch_voice_unhandled));
+
+ // [ANALYTICS EVENT]: Launch Voice Unhandled
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_voice,
+ R.string.analytics_launch_voice_unhandled);
+ } else {
+ setAlreadyHandled(intent);
+ }
+ }
+ }
+
+ /**
+ * This method is responsible for handling a corner case.
+ * Upon orientation change, the Activity is re-created (onCreate is called)
+ * and the same intent is (re)delivered to the app.
+ * Fortunately the Intent is Parcelable so we can mark it and check for this condition.
+ * Without this, if the phone is in portrait mode, and the user issues voice command to
+ * start a game (or other forcing orientation change), the following happens:
+ *
+ * 1. com.google.android.apps.santatracker.PLAY_GAME is delivered to the app.
+ * 2. Game is started and phone switches to landscape.
+ * 3. User ends the game, rotates the phone back to portrait.
+ * 4. onCreate is called again since StartupActivity is re-created.
+ * 5. The voice action is re-executed
+ * (since getIntent returns com.google.android.apps.santatracker.PLAY_GAME).
+ *
+ * We don't want #5 to take place.
+ *
+ * @param intent current intent
+ */
+ private void setAlreadyHandled(Intent intent) {
+ intent.putExtra(INTENT_HANDLED, true);
+ }
+
+ /**
+ * Checks to see if the intent (voice command) has already been processed
+ *
+ * @param intent current intent (voice command)
+ * @return true if the intent (voice command) has already been processed
+ */
+ private boolean isAlreadyHandled(Intent intent) {
+ return intent.getBooleanExtra(INTENT_HANDLED, false);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mHaveGooglePlayServices) {
+ mCastManager = SantaApplication.getCastManager(this);
+ mCastManager.incrementUiCounter();
+ }
+
+ mResumed = true;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mResumed = false;
+ mAudioPlayer.stopAll();
+
+ cancelUIUpdate();
+
+ if (mCastManager != null) {
+ mCastManager.decrementUiCounter();
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ registerWithService();
+
+ // Check for App Invites
+ mInvitesFragment.getInvite(new AppInvitesFragment.GetInvitationCallback() {
+ @Override
+ public void onInvitation(String invitationId, String deepLink) {
+ Log.d(TAG, "onInvitation: " + deepLink);
+ }
+ }, true);
+
+ initialiseViews();
+ resetLauncherStates();
+ }
+
+ private void resetLauncherStates() {
+ // Start only if play services are available
+ if (mHaveGooglePlayServices) {
+ stateNoData();
+ } else {
+ hideStatus();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unregisterFromService();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mGamesFragment.onActivityResult(requestCode, resultCode, data);
+ }
+
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mResumed && hasFocus && !AccessibilityUtil.isTouchAccessiblityEnabled(this)) {
+ mAudioPlayer.playTrackExclusive(R.raw.village_music, true);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu
+ getMenuInflater().inflate(R.menu.menu_startup, menu);
+
+ // Add cast button
+ if (mCastManager != null) {
+ mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
+ }
+
+ mMenuItemLegal = menu.findItem(R.id.legal);
+ mMenuItemLegal.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final Resources resources = getResources();
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(StartupActivity.this);
+ dialogBuilder.setItems(resources.getStringArray(R.array.legal_privacy),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ String url;
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
+ StartupActivity.this);
+ switch (which) {
+ case 1:
+ // Privacy
+ url = resources.getString(R.string.url_privacy);
+ break;
+ case 2:
+ // TOS
+ url = resources.getString(R.string.url_tos);
+ break;
+ case 3:
+ // TOS
+ url = resources.getString(R.string.url_seismic);
+ break;
+ case 4:
+ // Show play services license text
+ dialog.dismiss();
+ dialogBuilder.setMessage(GooglePlayServicesUtil
+ .getOpenSourceSoftwareLicenseInfo(
+ getApplicationContext())).create().show();
+ dialogBuilder.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ dialog.dismiss();
+ }
+ });
+ return;
+ case 0:
+ default:
+ url = resources.getString(R.string.url_legal);
+ break;
+ }
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+ });
+ dialogBuilder.create().show();
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.menu_app_invite)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ mInvitesFragment.sendGenericInvite();
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.open_help)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ startActivity(new Intent(Intent.ACTION_VIEW,
+ Uri.parse(getString(R.string.url_help))));
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.github_santa)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ startActivity(new Intent(Intent.ACTION_VIEW,
+ Uri.parse(getString(R.string.url_github_santa))));
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.github_pienoon)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ startActivity(new Intent(Intent.ACTION_VIEW,
+ Uri.parse(getString(R.string.url_github_pienoon))));
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.sign_out)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ Games.signOut(mGamesFragment.getGamesApiClient());
+ updateSignInState(false);
+ return true;
+ }
+ });
+
+ // TODO Temp menu items for testing notifications
+ menu.findItem(R.id.notification_nearby)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ SantaNotificationBuilder.CreateSantaNotification(StartupActivity.this,
+ R.string.notification_nearby);
+ /*
+ Intent wearIntent = new Intent(StartupActivity.this,
+ PhoneNotificationService.class);
+ wearIntent.setAction(NotificationConstants.ACTION_SEND);
+ wearIntent.putExtra(NotificationConstants.KEY_CONTENT,
+ StartupActivity.this.getResources()
+ .getString(R.string.notification_nearby));
+ StartupActivity.this.startService(wearIntent);
+ */
+ return true;
+ }
+ });
+ menu.findItem(R.id.notification_takeoff)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ SantaNotificationBuilder
+ .CreateSantaNotification(StartupActivity.this,
+ R.string.notification_takeoff);
+ /*
+ Intent wearIntent = new Intent(StartupActivity.this,
+ PhoneNotificationService.class);
+ wearIntent.setAction(NotificationConstants.ACTION_SEND);
+ wearIntent.putExtra(NotificationConstants.KEY_CONTENT,
+ StartupActivity.this.getResources()
+ .getString(R.string.notification_takeoff));
+ StartupActivity.this.startService(wearIntent);
+ */
+ return true;
+ }
+ });
+ menu.findItem(R.id.notification_location)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ /*
+ SantaNotificationBuilder
+ .CreateTriviaNotification(StartupActivity.this, "location",
+ "photoUrl", "mapUrl", "fact");
+ */
+ SantaNotificationBuilder
+ .CreateInfoNotification(StartupActivity.this, "Title", "text",
+ "https://www.google.com/images/srpr/logo11w.png");
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.launch_mode)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ enableTrackerMode(true);
+ return true;
+ }
+ });
+
+ menu.findItem(R.id.countdown_mode)
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ startCountdown(OFFLINE_SANTA_DEPARTURE);
+ return true;
+ }
+ });
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ protected boolean onPrepareOptionsPanel(View view, Menu menu) {
+ menu.findItem(R.id.sign_out).setVisible(mSignedIn);
+ return super.onPrepareOptionsPanel(view, menu);
+ }
+
+ private void setCastDisabled(boolean disableCast) {
+ if (mCastManager == null || !mHaveGooglePlayServices) {
+ return;
+ }
+ // Enable or disable casting
+ SantaApplication.toogleCast(this, disableCast);
+ }
+
+ /**
+ * Move to 'no valid data' state ("offline"). No further locations, rely on local offline data
+ * only.
+ */
+ private void stateNoData() {
+ Log.d(TAG, "Santa is offline.");
+
+ // Enable/disable pins and nav drawer
+ updateNavigation();
+
+ // Schedule UI Updates
+ scheduleUIUpdate();
+
+ // Disable cast only if not already casting
+ if (mHaveGooglePlayServices && mCastManager != null && !mCastManager.isConnected()) {
+ setCastDisabled(true);
+ }
+
+ // Note that in the "no data" state, this may or may not include the TIME_OFFSET, depending
+ // on whether we've had a successful API call and still have the data. We can't use
+ // System.currentTimeMillis() as it *will* ignore TIME_OFFSET.
+ final long time = SantaPreferences.getCurrentTime();
+
+ AbstractLaunch launchSanta = mCardAdapter.getLauncher(CardAdapter.SANTA);
+
+ if (time < OFFLINE_SANTA_DEPARTURE) {
+ // Santa hasn't departed yet, show countdown
+ launchSanta.setState(AbstractLaunch.STATE_LOCKED);
+ startCountdown(OFFLINE_SANTA_DEPARTURE);
+ final long notificationTime = SantaPreferences
+ .getAdjustedTime(OFFLINE_SANTA_DEPARTURE);
+ SantaNotificationBuilder
+ .ScheduleSantaNotification(getApplicationContext(), notificationTime,
+ NotificationConstants.NOTIFICATION_TAKEOFF);
+
+ } else if (time >= OFFLINE_SANTA_DEPARTURE && time < OFFLINE_SANTA_FINALARRIVAL) {
+ // Santa should have already left, but no data yet, hide countdown and show message
+ stopCountdown();
+ enableTrackerMode(false);
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ showStatus(R.string.contacting_santa);
+ } else {
+ // Post Christmas
+ stopCountdown();
+ enableTrackerMode(false);
+ launchSanta.setState(AbstractLaunch.STATE_FINISHED);
+ }
+ }
+
+
+ /**
+ * Move to 'data' (online) state.
+ */
+ private void stateData() {
+ Log.d(TAG, "Santa is online.");
+
+ // Enable/disable pins and nav drawer
+ updateNavigation();
+
+ // Schedule next UI update
+ scheduleUIUpdate();
+
+ // hide status
+ hideStatus();
+
+ // Set cast state
+ setCastDisabled(mFlagDisableCast);
+
+ long time = SantaPreferences.getCurrentTime();
+
+ AbstractLaunch launchSanta = mCardAdapter.getLauncher(CardAdapter.SANTA);
+ // Is Santa finished?
+ if (time > mFirstDeparture && time < OFFLINE_SANTA_FINALARRIVAL) {
+ // Santa should be travelling, enable map and hide countdown
+ enableTrackerMode(true);
+
+ // Schedule stream notifications
+ SantaNotificationBuilder.ScheduleNotificationNotification(this);
+
+ if (mFlagSwitchOff) {
+ // Kill-switch triggered, disable button
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ } else if (time > mFinalArrival) {
+ // No data
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ showStatus(R.string.still_trying_to_reach_santa);
+ } else {
+ launchSanta.setState(AbstractLaunch.STATE_READY);
+ }
+
+ } else if (time < mFirstDeparture) {
+ // Santa hasn't taken off yet, start count-down and schedule
+ // notification to first departure, hide buttons
+ final long notificationTime = SantaPreferences
+ .getAdjustedTime(mFirstDeparture);
+ SantaNotificationBuilder.ScheduleSantaNotification(getApplicationContext(),
+ notificationTime,
+ NotificationConstants.NOTIFICATION_TAKEOFF);
+ // Schedule stream notifications
+ SantaNotificationBuilder.ScheduleNotificationNotification(this);
+
+ startCountdown(mFirstDeparture);
+ launchSanta.setState(AbstractLaunch.STATE_LOCKED);
+
+ } else {
+ // Post Christmas, hide countdown and buttons
+ launchSanta.setState(AbstractLaunch.STATE_FINISHED);
+ stopCountdown();
+ enableTrackerMode(false);
+ }
+
+ }
+
+ public void enableTrackerMode(boolean showLaunchButton) {
+ mCountdown.cancel();
+ mVillageBackdrop.setImageResource(R.drawable.village_bg_launch);
+ mVillage.setPlaneEnabled(false);
+ mLaunchButton.setVisibility(showLaunchButton ? View.VISIBLE : View.GONE);
+ mSantaCollapsing.setOverlayColor(R.color.villageToolbarDark);
+ mCountdownView.setVisibility(View.GONE);
+ mWavingSanta.setVisibility(View.GONE);
+ mOrnament.setVisibility(View.GONE);
+ }
+
+ public void startCountdown(long time) {
+ mCountdown.startTimer(time - SantaPreferences.getCurrentTime());
+ mVillageBackdrop.setImageResource(R.drawable.village_bg_countdown);
+ mVillage.setPlaneEnabled(true);
+ mLaunchButton.setVisibility(View.GONE);
+ mSantaCollapsing.setOverlayColor(R.color.villageToolbarLight);
+ mCountdownView.setVisibility(View.VISIBLE);
+ mWavingSanta.setVisibility(View.VISIBLE);
+ mOrnament.setVisibility(View.VISIBLE);
+ }
+
+ public void stopCountdown() {
+ mCountdown.cancel();
+ mCountdownView.setVisibility(View.GONE);
+ }
+
+ /*
+ * Village Markers
+ */
+ private void updateNavigation() {
+ // Games
+ mCardAdapter.getLauncher(CardAdapter.GUMBALL).setState(
+ getGamePinState(mFlagDisableGumball, UNLOCK_GUMBALL));
+ mCardAdapter.getLauncher(CardAdapter.MEMORY).setState(
+ getGamePinState(mFlagDisableMemory, UNLOCK_MEMORY));
+ mCardAdapter.getLauncher(CardAdapter.JETPACK)
+ .setState(getGamePinState(mFlagDisableJetpack, UNLOCK_JETPACK));
+ mCardAdapter.getLauncher(CardAdapter.ROCKET).setState(
+ getGamePinState(mFlagDisableRocket, UNLOCK_ROCKET));
+ mCardAdapter.getLauncher(CardAdapter.DANCER).setState(
+ getGamePinState(mFlagDisableDancer, UNLOCK_DANCER));
+ mCardAdapter.getLauncher(CardAdapter.SNOWDOWN).setState(
+ getGamePinState(mFlagDisableSnowdown, UNLOCK_SNOWDOWN));
+
+ ((LaunchVideo) mCardAdapter.getLauncher(CardAdapter.VIDEO01)).setVideo(
+ mVideoList[0], UNLOCK_VIDEO_1);
+ ((LaunchVideo) mCardAdapter.getLauncher(CardAdapter.VIDEO15)).setVideo(
+ mVideoList[1], UNLOCK_VIDEO_15);
+ ((LaunchVideo) mCardAdapter.getLauncher(CardAdapter.VIDEO23)).setVideo(
+ mVideoList[2], UNLOCK_VIDEO_23);
+
+ // reinitialise action bar
+ supportInvalidateOptionsMenu();
+ }
+
+ private int getGamePinState(boolean disabledFlag, long unlockTime) {
+ if (disabledFlag) {
+ return AbstractLaunch.STATE_HIDDEN;
+ } else if (!disabledFlag && SantaPreferences.getCurrentTime() < unlockTime) {
+ return AbstractLaunch.STATE_LOCKED;
+ } else {
+ return AbstractLaunch.STATE_READY;
+ }
+ }
+
+ /*
+ * Status Message
+ */
+
+ private void showStatus(int i) {
+ int state = mCardAdapter.getLauncher(CardAdapter.SANTA).getState();
+ if (state == AbstractLaunch.STATE_DISABLED || state == AbstractLaunch.STATE_LOCKED) {
+
+ mStatusText.setVisibility(View.VISIBLE);
+ mStatusText.setText(i);
+ mStatusText.setContentDescription(mStatusText.getText());
+ }
+ }
+
+ private void hideStatus() {
+ mStatusText.setVisibility(View.GONE);
+ }
+
+ /*
+ * Scheduled UI update
+ */
+
+ /**
+ * Schedule a call to {@link #stateData()} or {@link #stateNoData()} at the next time at which
+ * the UI should be updated (games become available, Santa takes off, Santa is finished).
+ */
+ private void scheduleUIUpdate() {
+ // cancel scheduled update
+ cancelUIUpdate();
+
+ final long delay = calculateNextUiUpdateDelay();
+ if (delay > 0 && delay < Long.MAX_VALUE) {
+ // schedule if delay is in the future
+ mHandler.postDelayed(mUpdateUiRunnable, delay);
+ }
+ }
+
+ private long calculateNextUiUpdateDelay() {
+
+ final long time = SantaPreferences.getCurrentTime();
+
+ final long departureDelay = mFirstDeparture - time;
+ final long arrivalDelay = mFinalArrival - time;
+
+ // if disable flag is toggled, exclude from calculation
+ final long[] delays = new long[]{
+ mFlagDisableGumball ? Long.MAX_VALUE : UNLOCK_GUMBALL - time,
+ mFlagDisableJetpack ? Long.MAX_VALUE : UNLOCK_JETPACK - time,
+ mFlagDisableMemory ? Long.MAX_VALUE : UNLOCK_MEMORY - time,
+ mFlagDisableRocket ? Long.MAX_VALUE : UNLOCK_ROCKET - time,
+ mFlagDisableDancer ? Long.MAX_VALUE : UNLOCK_DANCER - time,
+ mFlagDisableSnowdown ? Long.MAX_VALUE : UNLOCK_SNOWDOWN - time,
+ departureDelay, arrivalDelay};
+
+ // find lowest delay, but only count positive values or zero (ie. that are in the future)
+ long delay = Long.MAX_VALUE;
+ for (final long x : delays) {
+ if (x >= 0) {
+ delay = Math.min(delay, x);
+ }
+ }
+
+ return delay;
+ }
+
+ private void cancelUIUpdate() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ private Runnable mUpdateUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mWaitingForApi) {
+ stateData();
+ } else {
+ stateNoData();
+ }
+ }
+ };
+
+ private void updateSignInState(boolean signedIn) {
+ mSignedIn = signedIn;
+ setFabVisibility((FloatingActionButton) findViewById(R.id.fab_leaderboard), signedIn);
+ setFabVisibility((FloatingActionButton) findViewById(R.id.fab_achievement), signedIn);
+ supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onSignInFailed() {
+ updateSignInState(false);
+ }
+
+ @Override
+ public void onSignInSucceeded() {
+ updateSignInState(true);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.launch_button:
+ launchTracker();
+ break;
+ case R.id.santa_waving:
+ animateSanta();
+ break;
+ case R.id.fab_achievement:
+ showAchievements();
+ break;
+ case R.id.fab_leaderboard:
+ showLeaderboards();
+ break;
+ }
+ }
+
+ private void animateSanta() {
+ boolean play = false;
+ if (mWavingAnim == null) {
+ mWavingAnim = AnimationUtils.loadAnimation(this, R.anim.santa_wave);
+ play = true;
+ } else if (mWavingAnim.hasEnded()) {
+ play = true;
+ }
+
+ if (play) {
+ playSoundOnce(R.raw.ho_ho_ho);
+ mWavingSantaArm.startAnimation(mWavingAnim);
+ }
+ }
+
+ private void showAchievements() {
+ GoogleApiClient apiClient = mGamesFragment.getGamesApiClient();
+ if (apiClient != null && apiClient.isConnected()) {
+ startActivityForResult(
+ Games.Achievements.getAchievementsIntent(apiClient),
+ RC_GAMES);
+ }
+ }
+
+ private void showLeaderboards() {
+ GoogleApiClient apiClient = mGamesFragment.getGamesApiClient();
+ if (apiClient != null && apiClient.isConnected()) {
+ startActivityForResult(
+ Games.Leaderboards.getAllLeaderboardsIntent(apiClient),
+ RC_GAMES);
+ }
+ }
+
+ /**
+ * Shows/hides one of the FloatingActionButtons. This sets both the visibility and the
+ * appropriate anchor, which is required to keep the FAB hidden.
+ */
+ private void setFabVisibility(FloatingActionButton fab, boolean show) {
+ CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+ if (show) {
+ p.setAnchorId(R.id.appbar);
+ fab.setLayoutParams(p);
+ fab.setVisibility(View.VISIBLE);
+ } else {
+ p.setAnchorId(View.NO_ID);
+ fab.setLayoutParams(p);
+ fab.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void playSoundOnce(int resSoundId) {
+ mAudioPlayer.playTrack(resSoundId, false);
+ }
+
+ @Override
+ public Context getActivityContext() {
+ return this;
+ }
+
+ @Override
+ public void launchActivity(Intent intent) {
+ launchActivityInternal(intent, 0);
+ }
+
+ @Override
+ public void launchActivityDelayed(final Intent intent, View v) {
+ launchActivityInternal(intent, 200);
+ }
+
+ @Override
+ public View getCountdownView() {
+ return findViewById(R.id.countdown_container);
+ }
+
+ @Override
+ public void onCountdownFinished() {
+ if (!mWaitingForApi) {
+ stateData();
+ } else {
+ stateNoData();
+ }
+ }
+
+ /** Attempt to launch the tracker, if available. */
+ public void launchTracker() {
+ AbstractLaunch launch = mCardAdapter.getLauncher(CardAdapter.SANTA);
+ if (launch instanceof LaunchSanta) {
+ LaunchSanta tracker = (LaunchSanta) launch;
+
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_village);
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_village));
+
+ tracker.onClick(tracker.getClickTarget());
+ }
+ }
+
+ /*
+ * Service communication
+ */
+
+ class IncomingHandler extends Handler {
+
+ /*
+ Order in which messages are received: Data updates, State of Service [Idle, Error]
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ SantaLog.d(TAG, "message=" + msg.what);
+ switch (msg.what) {
+ case SantaServiceMessages.MSG_SERVICE_STATUS:
+ // Current state of service, received once when connecting
+ onSantaServiceStateUpdate(msg.arg1);
+ break;
+ case SantaServiceMessages.MSG_INPROGRESS_UPDATE_ROUTE:
+ // route is about to be updated
+ onRouteUpdateStart();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_ROUTE:
+ // route data has been updated
+ onRouteDataUpdateFinished();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_ONOFF:
+ mFlagSwitchOff = (msg.arg1 == SantaServiceMessages.SWITCH_OFF);
+ onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_TIMES:
+ Bundle b = (Bundle) msg.obj;
+ mOffset = b.getLong(SantaServiceMessages.BUNDLE_OFFSET);
+ SantaPreferences.cacheOffset(mOffset);
+ mFinalArrival = b.getLong(SantaServiceMessages.BUNDLE_FINAL_ARRIVAL);
+ mFirstDeparture = b.getLong(SantaServiceMessages.BUNDLE_FIRST_DEPARTURE);
+ onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_CASTDISABLED:
+ mFlagDisableCast = (msg.arg1 == SantaServiceMessages.DISABLED);
+ onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_GAMES:
+ final int arg = msg.arg1;
+ mFlagDisableGumball = (arg & SantaServiceMessages.MSG_FLAG_GAME_GUMBALL)
+ == SantaServiceMessages.MSG_FLAG_GAME_GUMBALL;
+ mFlagDisableJetpack = (arg & SantaServiceMessages.MSG_FLAG_GAME_JETPACK)
+ == SantaServiceMessages.MSG_FLAG_GAME_JETPACK;
+ mFlagDisableMemory = (arg & SantaServiceMessages.MSG_FLAG_GAME_MEMORY)
+ == SantaServiceMessages.MSG_FLAG_GAME_MEMORY;
+ mFlagDisableRocket = (arg & SantaServiceMessages.MSG_FLAG_GAME_ROCKET)
+ == SantaServiceMessages.MSG_FLAG_GAME_ROCKET;
+ mFlagDisableDancer = (arg & SantaServiceMessages.MSG_FLAG_GAME_DANCER)
+ == SantaServiceMessages.MSG_FLAG_GAME_DANCER;
+ mFlagDisableSnowdown = (arg & SantaServiceMessages.MSG_FLAG_GAME_SNOWDOWN)
+ == SantaServiceMessages.MSG_FLAG_GAME_SNOWDOWN;
+ onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_VIDEOS:
+ Bundle data = msg.getData();
+ mVideoList = data.getStringArray(SantaServiceMessages.BUNDLE_VIDEOS);
+ onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_ERROR:
+ // Error accessing the API, ignore because there is data.
+ onApiSuccess();
+ break;
+ case SantaServiceMessages.MSG_ERROR_NODATA:
+ stateNoData();
+ break;
+ case SantaServiceMessages.MSG_SUCCESS:
+ onApiSuccess();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+
+ }
+ }
+
+ /**
+ * Handle the state of the SantaService when first connecting to it.
+ */
+ private void onSantaServiceStateUpdate(int state) {
+ switch (state) {
+ case SantaServiceMessages.STATUS_IDLE:
+ // Service is idle, data should be uptodate
+ mWaitingForApi = false;
+ stateData();
+ break;
+ case SantaServiceMessages.STATUS_IDLE_NODATA:
+ mWaitingForApi = true;
+ stateNoData();
+ break;
+ case SantaServiceMessages.STATUS_ERROR_NODATA:
+ // Service is in error state and there is no valid data
+ mWaitingForApi = true;
+ stateNoData();
+ case SantaServiceMessages.STATUS_ERROR:
+ // Service is in error state and waiting for another attempt to access API
+ mWaitingForApi = true;
+ stateNoData();
+ case SantaServiceMessages.STATUS_PROCESSING:
+ // Service is busy processing an update, wait for success and ignore this state
+ mWaitingForApi = true;
+ break;
+
+ }
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = new Messenger(service);
+
+ //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;
+ }
+ };
+
+
+ private void onApiSuccess() {
+
+ if (mWaitingForApi) {
+ mWaitingForApi = false;
+ stateData();
+ }
+ }
+
+ private void onRouteDataUpdateFinished() {
+ // switch to 'online' mode, data has been loaded
+ if (!mWaitingForApi) {
+ stateData();
+ }
+ }
+
+ private void onRouteUpdateStart() {
+ // temporarily switch back to offline mode until route update has finished
+ if (!mWaitingForApi) {
+ stateNoData();
+ }
+ }
+
+ private void onDataUpdate() {
+ if (!mWaitingForApi) {
+ stateData();
+ }
+ }
+
+
+ private void registerWithService() {
+ bindService(new Intent(this, SantaService.class), mConnection, Context.BIND_AUTO_CREATE);
+ mIsBound = true;
+ }
+
+ 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
+ }
+ }
+ unbindService(mConnection);
+ mIsBound = false;
+ }
+ }
+
+ private synchronized void launchActivityInternal(
+ final Intent intent, long delayMs) {
+
+ if (!mLaunchingChild) {
+ mLaunchingChild = true;
+
+ // stop timer
+ if (mCountdown != null) {
+ mCountdown.cancel();
+ }
+
+ SantaNotificationBuilder.DismissNotifications(getApplicationContext());
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ startActivityForResult(intent, RC_STARTUP);
+ mLaunchingChild = false;
+ }
+ }, delayMs);
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StickyScrollListener.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StickyScrollListener.java
new file mode 100644
index 000000000..1453b63e2
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/StickyScrollListener.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * Scroll listener that adjusts the RecyclerView after a scroll event to make sure that the
+ * top list item is fully visible and not obscured.
+ */
+public class StickyScrollListener extends RecyclerView.OnScrollListener {
+
+ private static final String TAG = "StickyScrollListener";
+
+ /**
+ * Cutoff for stickiness. If less than this fraction of the obscured view can be seen, the
+ * RecyclerView is scrolled to a more visible view. Otherwise, this view is scrolled to be
+ * fully visible.
+ */
+ private static final float VISIBILITY_CUTOFF = 0.60f;
+
+ private static final int UP = 1;
+ private static final int DOWN = 2;
+
+ private int mScrollDirection = 0;
+
+ /** LinearLayoutManager controlling the RecyclerView **/
+ private LinearLayoutManager mManager;
+
+ /** Number of columns displayed by the Manager **/
+ private int mNumColumns = 1;
+
+ /** Scroll state of the RecyclerView **/
+ private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+
+ /** Position of the first completely visible view **/
+ private int topVisiblePos;
+
+ /** Position of the view above the first completely visible view **/
+ private int topObscuredPos;
+
+ /** Position of the last completely visible view **/
+ private int bottomVisiblePos;
+
+ /** Position of the view below the last completely visible view **/
+ private int bottomObscuredPos;
+
+ /** View above the first completely visible View **/
+ private View topObscuredView;
+
+ /** View below the last completely visible View **/
+ private View bottomObscuredView;
+
+ /** Percent of topObscuredView that is visible **/
+ private float topObscuredPercentVisible;
+
+ /** Percent of bottomObscuredView that is visible **/
+ private float bottomObscuredPercentVisible;
+
+ public StickyScrollListener(LinearLayoutManager manager, int numColumns) {
+ this.mManager = manager;
+ this.mNumColumns = numColumns;
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ topVisiblePos = mManager.findFirstCompletelyVisibleItemPosition();
+ topObscuredPos = topVisiblePos - mNumColumns;
+ topObscuredView = mManager.findViewByPosition(topObscuredPos);
+
+ bottomVisiblePos = mManager.findLastCompletelyVisibleItemPosition();
+ bottomObscuredPos = bottomVisiblePos + mNumColumns;
+ bottomObscuredView = mManager.findViewByPosition(bottomObscuredPos);
+
+ if (topObscuredView != null) {
+ // Calculate how many pixels of the obscured view are visible.
+ float topObscuredPixelsVisible = (topObscuredView.getHeight() + topObscuredView.getY());
+
+ // Calculate what percentage of the obscured view is visible.
+ topObscuredPercentVisible = topObscuredPixelsVisible / topObscuredView.getHeight();
+ } else {
+ clearTop();
+ }
+
+ if (bottomObscuredView != null) {
+ // Same calculation for bottom
+ float bottomObscuredPixelsVisible = (recyclerView.getHeight() - bottomObscuredView.getY());
+ bottomObscuredPercentVisible = bottomObscuredPixelsVisible / bottomObscuredView.getHeight();
+ } else {
+ clearBottom();
+ }
+
+ // Mark scroll direction
+ if (dy <= 0) {
+ mScrollDirection = UP;
+ } else {
+ mScrollDirection = DOWN;
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ // This detects the end of a non-fling drag
+ boolean stoppedDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
+ newState == RecyclerView.SCROLL_STATE_IDLE;
+
+ boolean stoppedFlinging = mScrollState == RecyclerView.SCROLL_STATE_SETTLING &&
+ newState == RecyclerView.SCROLL_STATE_IDLE;
+
+ if (stoppedDragging || stoppedFlinging) {
+ if (mScrollDirection == DOWN) {
+ if (topObscuredPercentVisible <= VISIBILITY_CUTOFF) {
+ // Scroll down to the top of the first completely visible view.
+ float abovePixelsVisible = topObscuredView.getY() + topObscuredView.getHeight();
+ recyclerView.smoothScrollBy(0, (int) abovePixelsVisible);
+ } else if (topObscuredPos >= 0) {
+ // Scroll up to the top of the view above the first completely visible view.
+ recyclerView.smoothScrollBy(0, (int) topObscuredView.getY());
+ }
+ }
+ }
+
+ // Mark the scroll state to avoid duplicate event detection.
+ mScrollState = newState;
+ }
+
+
+ private void clearTop() {
+ topObscuredView = null;
+ topObscuredPos = -1;
+ topVisiblePos = -1;
+ topObscuredPercentVisible = 1.0f;
+ }
+
+ private void clearBottom() {
+ bottomObscuredView = null;
+ bottomObscuredPos = -1;
+ bottomVisiblePos = -1;
+ bottomObscuredPercentVisible = 1.0f;
+ }
+}
+
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardAdapter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardAdapter.java
new file mode 100644
index 000000000..9d988b241
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.Presenter;
+
+import com.google.android.apps.santatracker.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class TvCardAdapter extends ArrayObjectAdapter implements LauncherDataChangedCallback {
+
+ public static final int SANTA = 0;
+ public static final int VIDEO01 = 1;
+ public static final int JETPACK = 2;
+ public static final int VIDEO15 = 3;
+ public static final int ROCKET = 4;
+ public static final int SNOWDOWN = 5;
+ public static final int VIDEO23 = 6;
+
+ public static final int NUM_PINS = 7;
+
+ private AbstractLaunch[] mAllLaunchers = new AbstractLaunch[NUM_PINS];
+ private AbstractLaunch[] mLaunchers;
+
+ public TvCardAdapter(SantaContext santaContext, Presenter presenter) {
+ super(presenter);
+
+ mAllLaunchers[SANTA] = new LaunchSanta(santaContext, this);
+ mAllLaunchers[VIDEO01] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_santas_back, 1);
+ mAllLaunchers[JETPACK] = new LaunchJetpack(santaContext, this);
+ mAllLaunchers[VIDEO15] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_office_prank, 1);
+ mAllLaunchers[ROCKET] = new LaunchRocket(santaContext, this);
+ mAllLaunchers[SNOWDOWN] = new LaunchSnowdown(santaContext, this);
+ mAllLaunchers[VIDEO23] = new LaunchVideo(santaContext, this,
+ R.drawable.android_game_cards_elf_car, 23);
+
+ setHasStableIds(false);
+ refreshData();
+ }
+
+ public void updateMarkerVisibility() {
+ List launchers = new ArrayList<>(NUM_PINS);
+ for (AbstractLaunch l : mAllLaunchers) {
+ if (l.getState() != AbstractLaunch.STATE_HIDDEN) {
+ launchers.add(l);
+ }
+ }
+ mLaunchers = launchers.toArray(new AbstractLaunch[launchers.size()]);
+ }
+
+ public AbstractLaunch[] getLaunchers() {
+ return mAllLaunchers;
+ }
+
+ public AbstractLaunch getLauncher(int i) {
+ return mAllLaunchers[i];
+ }
+
+ @Override
+ public void refreshData() {
+ updateMarkerVisibility();
+ clear();
+ addAll(0, Arrays.asList(mLaunchers));
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardPresenter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardPresenter.java
new file mode 100644
index 000000000..c7a208a2f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvCardPresenter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 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.graphics.Typeface;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.google.android.apps.santatracker.R;
+
+public class TvCardPresenter extends Presenter {
+
+ static class TvLaunchViewHolder extends RowPresenter.ViewHolder {
+
+ public AbstractLaunch launcher;
+ public ImageView backgroundImageView;
+ public TextView nameView;
+ public TextView verbView;
+ public View lockedView;
+
+ public TvLaunchViewHolder(View itemView) {
+ super(itemView);
+
+ backgroundImageView = (ImageView) itemView.findViewById(R.id.card_background_image);
+ nameView = (TextView) itemView.findViewById(R.id.card_name_text);
+ verbView = (TextView) itemView.findViewById(R.id.card_verb);
+ lockedView = itemView.findViewById(R.id.card_disabled);
+ }
+
+ public void setLauncher(AbstractLaunch launcher, Typeface tf) {
+ this.launcher = launcher;
+
+ // Loading all of these beautiful images at full res is laggy without using
+ // Glide, however this makes it asynchronous. We should consider either compromising
+ // on image resolution or doing some sort of nifty placeholder.
+ Glide.with(view.getContext())
+ .fromResource()
+ .centerCrop()
+ .load(launcher.getCardResource())
+ .into(backgroundImageView);
+
+ nameView.setText(launcher.getContentDescription());
+ verbView.setText(launcher.getVerb());
+ verbView.setTypeface(tf, Typeface.ITALIC);
+ view.setContentDescription(launcher.getContentDescription());
+
+ if (launcher.getState() == AbstractLaunch.STATE_DISABLED
+ || launcher.getState() == AbstractLaunch.STATE_LOCKED
+ || launcher.getState() == AbstractLaunch.STATE_FINISHED) {
+ lockedView.setVisibility(View.VISIBLE);
+ } else {
+ lockedView.setVisibility(View.GONE);
+ }
+
+ if (launcher.getState() != AbstractLaunch.STATE_HIDDEN) {
+ launcher.attachToView(this.view);
+ }
+ }
+ }
+
+ private final Typeface mFont;
+
+ public TvCardPresenter(SantaContext context) {
+ mFont = Typeface.createFromAsset(context.getActivityContext().getAssets(),
+ "Lobster-Regular.otf");
+ }
+
+ @Override
+ public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+
+ final View view = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.layout_village_card_tv, parent, false);
+
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(false);
+
+ return new TvLaunchViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+
+ final AbstractLaunch launch = (AbstractLaunch) item;
+ final TvLaunchViewHolder holder = (TvLaunchViewHolder) viewHolder;
+ holder.setLauncher(launch, mFont);
+ holder.launcher.applyState();
+
+ if (holder.launcher.getState() == AbstractLaunch.STATE_DISABLED) {
+ // TODO: Blur or darken the image
+ }
+ }
+
+ @Override
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvStartupActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvStartupActivity.java
new file mode 100644
index 000000000..eee27e7b4
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/TvStartupActivity.java
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2015 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.Manifest;
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+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.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.FocusHighlight;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.load.DecodeFormat;
+import com.google.android.apps.santatracker.AudioPlayer;
+import com.google.android.apps.santatracker.BuildConfig;
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.invites.AppInvitesFragment;
+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.MeasurementManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.apps.santatracker.village.Village;
+import com.google.android.apps.santatracker.village.VillageView;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Launch activity for the app. Handles loading of the village, the state of the markers (based on
+ * the date/time) and incoming voice intents.
+ */
+public class TvStartupActivity extends FragmentActivity implements
+ View.OnClickListener, Village.VillageListener,
+ SantaContext, LaunchCountdown.LaunchCountdownContext {
+
+ protected static final String TAG = "SantaStart";
+ private static final String VILLAGE_TAG = "VillageFragment";
+
+ private AppInvitesFragment mInvitesFragment;
+ private AudioPlayer mAudioPlayer;
+
+ private boolean mResumed = false;
+
+ private boolean mIsDebug = false;
+
+ private Village mVillage;
+ private VillageView mVillageView;
+ private ImageView mVillageBackdrop;
+ private View mLaunchButton;
+ private View mCountdownView;
+
+ private VerticalGridView mMarkers;
+ private TvCardAdapter mCardAdapter;
+
+ private LaunchCountdown mCountdown;
+
+ // Load these values from resources when an instance of this activity is initialised.
+ private static long OFFLINE_SANTA_DEPARTURE;
+ private static long OFFLINE_SANTA_FINALARRIVAL;
+ private static long UNLOCK_JETPACK;
+ private static long UNLOCK_ROCKET;
+ private static long UNLOCK_SNOWDOWN;
+ private static long UNLOCK_VIDEO_1;
+ private static long UNLOCK_VIDEO_15;
+ private static long UNLOCK_VIDEO_23;
+
+ // Server controlled flags
+ private long mOffset = 0;
+ private boolean mFlagSwitchOff = false;
+ private boolean mFlagDisableJetpack = false;
+ private boolean mFlagDisableRocket = false;
+ private boolean mFlagDisableSnowdown = false;
+
+ private String[] mVideoList = new String[]{null, null, null};
+
+
+ private boolean mHaveGooglePlayServices = false;
+ private long mFirstDeparture;
+ private long mFinalArrival;
+
+ // Handler for scheduled UI updates
+ private Handler mHandler = new Handler();
+
+ // Waiting for data from the API (no data or data is outdated)
+ private boolean mWaitingForApi = true;
+
+ // Service integration
+ private Messenger mService = null;
+
+ private boolean mIsBound = false;
+ private Messenger mMessenger;
+
+ // request code for games Activities
+ private static final int RC_STARTUP = 1111;
+
+ // Permission request codes
+ private static final int RC_DEBUG_PERMS = 1;
+
+ private FirebaseAnalytics mMeasurement;
+ private boolean mLaunchingChild = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestPermissionsIfDebugModeEnabled();
+
+ // Glide's pretty aggressive at caching images, so get the 8888 preference in early.
+ if (!Glide.isSetup()) {
+ Glide.setup(new GlideBuilder(getActivityContext())
+ .setDecodeFormat(DecodeFormat.PREFER_ARGB_8888));
+ }
+
+ setContentView(R.layout.layout_startup_tv);
+ loadResourceFields(getResources());
+
+ mIsDebug = BuildConfig.DEBUG;
+
+ mMessenger= new Messenger(new IncomingHandler(this));
+
+ mCountdown = new LaunchCountdown(this);
+ mCountdownView = findViewById(R.id.countdown_container);
+ mAudioPlayer = new AudioPlayer(getApplicationContext());
+
+ mVillageView = (VillageView) findViewById(R.id.villageView);
+ mVillage = (Village) getSupportFragmentManager().findFragmentByTag(VILLAGE_TAG);
+ if (mVillage == null) {
+ mVillage = new Village();
+ getSupportFragmentManager().beginTransaction().add(mVillage, VILLAGE_TAG).commit();
+ }
+ mVillageBackdrop = (ImageView) findViewById(R.id.villageBackground);
+ mLaunchButton = findViewById(R.id.launch_button);
+ mLaunchButton.setOnClickListener(this);
+
+ mMarkers = (VerticalGridView) findViewById(R.id.markers);
+ initialiseViews();
+
+ mHaveGooglePlayServices = checkGooglePlayServicesAvailable();
+
+ // App invites
+ mInvitesFragment = AppInvitesFragment.getInstance(this);
+
+ // Initialize measurement
+ mMeasurement = FirebaseAnalytics.getInstance(this);
+ MeasurementManager.recordScreenView(mMeasurement,
+ getString(R.string.analytics_screen_village));
+
+ // [ANALYTICS SCREEN]: Village
+ AnalyticsManager.sendScreenView(R.string.analytics_screen_village);
+ // set the initial states
+ resetLauncherStates();
+ // See if it was a voice action which triggered this activity and handle it
+ onNewIntent(getIntent());
+ }
+
+ private void loadResourceFields(Resources res) {
+ final long ms = 1000L;
+ OFFLINE_SANTA_DEPARTURE = res.getInteger(R.integer.santa_takeoff) * ms;
+ OFFLINE_SANTA_FINALARRIVAL = res.getInteger(R.integer.santa_arrival) * ms;
+ mFinalArrival = OFFLINE_SANTA_FINALARRIVAL;
+ mFirstDeparture = OFFLINE_SANTA_DEPARTURE;
+
+ // Game unlock
+ UNLOCK_JETPACK = res.getInteger(R.integer.unlock_jetpack) * ms;
+ UNLOCK_ROCKET = res.getInteger(R.integer.unlock_rocket) * ms;
+ UNLOCK_SNOWDOWN = res.getInteger(R.integer.unlock_snowdown) * ms;
+
+ // Video unlock
+ UNLOCK_VIDEO_1 = res.getInteger(R.integer.unlock_video1) * ms;
+ UNLOCK_VIDEO_15 = res.getInteger(R.integer.unlock_video15) * ms;
+ UNLOCK_VIDEO_23 = res.getInteger(R.integer.unlock_video23) * ms;
+ }
+
+ void initialiseViews() {
+ mVillageView.setVillage(mVillage);
+
+ // Initialize ListRowPresenter
+ ListRowPresenter listRowPresenter = new ListRowPresenter(FocusHighlight.ZOOM_FACTOR_SMALL);
+ listRowPresenter.setShadowEnabled(true);
+ final int rowHeight
+ = getResources().getDimensionPixelOffset(R.dimen.tv_marker_height_and_shadow);
+ listRowPresenter.setRowHeight(rowHeight);
+
+ // Initialize ListRow
+ TvCardPresenter presenter = new TvCardPresenter(this);
+ mCardAdapter = new TvCardAdapter(this, presenter);
+ ListRow listRow = new ListRow(mCardAdapter);
+
+ // Initialize ObjectAdapter for ListRow
+ ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(listRowPresenter);
+ arrayObjectAdapter.add(listRow);
+
+ // Initialized Debug menus only for debug build.
+ if (mIsDebug) {
+ addDebugMenuListRaw(arrayObjectAdapter);
+ }
+
+ // set ItemBridgeAdapter to RecyclerView
+ ItemBridgeAdapter adapter = new ItemBridgeAdapter(arrayObjectAdapter);
+ mMarkers.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+ mMarkers.setAdapter(adapter);
+ }
+
+ private void addDebugMenuListRaw(ArrayObjectAdapter objectAdapter) {
+
+ mMarkers.setPadding(
+ mMarkers.getPaddingLeft(), mMarkers.getPaddingTop() + 150,
+ mMarkers.getPaddingRight(), mMarkers.getPaddingBottom());
+
+ Presenter debugMenuPresenter = new Presenter() {
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ TextView tv = new TextView(parent.getContext());
+ ViewGroup.MarginLayoutParams
+ params = new ViewGroup.MarginLayoutParams(200, 150);
+ tv.setLayoutParams(params);
+ tv.setGravity(Gravity.CENTER);
+ tv.setBackgroundColor(getResources().getColor(R.color.SantaBlueDark));
+ tv.setFocusableInTouchMode(false);
+ tv.setFocusable(true);
+ tv.setClickable(true);
+ return new ViewHolder(tv);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+ ((TextView)viewHolder.view).setText((String)item);
+ viewHolder.view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final String text = ((TextView)v).getText().toString();
+ if (text.contains("Enable Tracker")) {
+ enableTrackerMode(true);
+ } else if (text.contains("Enable CountDown")){
+ startCountdown(SantaPreferences.getCurrentTime());
+ } else {
+ mIsDebug = false;
+ initialiseViews();
+ resetLauncherStates();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onUnbindViewHolder(ViewHolder viewHolder) {
+
+ }
+ };
+
+ ObjectAdapter debugMenuAdapter = new ObjectAdapter(debugMenuPresenter) {
+
+ private final String[] mMenuString
+ = {"Enable Tracker", "Enable CountDown", "Hide DebugMenu"};
+ @Override
+ public int size() {
+ return mMenuString.length;
+ }
+
+ @Override
+ public Object get(int position) {
+ return mMenuString[position];
+ }
+ };
+
+ ListRow debugMenuListRow = new ListRow(debugMenuAdapter);
+ objectAdapter.add(debugMenuListRow);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void requestPermissionsIfDebugModeEnabled() {
+ // If debug mode is enabled in debug_settings.xml, and we don't yet have storage perms, ask.
+ if (getResources().getBoolean(R.bool.prompt_for_sdcard_perms)
+ && ContextCompat.checkSelfPermission(this,
+ Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+ RC_DEBUG_PERMS);
+ }
+ }
+
+ // see http://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances/
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ showColorMask(false);
+ mResumed = true;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mResumed = false;
+ mAudioPlayer.stopAll();
+
+ cancelUIUpdate();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ registerWithService();
+
+ // Check for App Invites
+ mInvitesFragment.getInvite(new AppInvitesFragment.GetInvitationCallback() {
+ @Override
+ public void onInvitation(String invitationId, String deepLink) {
+ Log.d(TAG, "onInvitation: " + deepLink);
+ }
+ }, true);
+
+ initialiseViews();
+ resetLauncherStates();
+ }
+
+ private void resetLauncherStates() {
+ // Start only if play services are available
+ if (mHaveGooglePlayServices) {
+ stateNoData();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unregisterFromService();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mResumed && hasFocus && !AccessibilityUtil.isTouchAccessiblityEnabled(this)) {
+ mAudioPlayer.playTrackExclusive(R.raw.village_music, true);
+ }
+ }
+
+ /**
+ * Move to 'no valid data' state ("offline"). No further locations, rely on local offline data
+ * only.
+ */
+ private void stateNoData() {
+ Log.d(TAG, "Santa is offline.");
+
+ // Enable/disable pins and nav drawer
+ updateNavigation();
+
+ // Schedule UI Updates
+ scheduleUIUpdate();
+
+ // Note that in the "no data" state, this may or may not include the TIME_OFFSET, depending
+ // on whether we've had a successful API call and still have the data. We can't use
+ // System.currentTimeMillis() as it *will* ignore TIME_OFFSET.
+ final long time = SantaPreferences.getCurrentTime();
+
+ AbstractLaunch launchSanta = mCardAdapter.getLauncher(CardAdapter.SANTA);
+
+ if (time < OFFLINE_SANTA_DEPARTURE) {
+ // Santa hasn't departed yet, show countdown
+ launchSanta.setState(AbstractLaunch.STATE_LOCKED);
+ startCountdown(OFFLINE_SANTA_DEPARTURE);
+ } else if (time >= OFFLINE_SANTA_DEPARTURE && time < OFFLINE_SANTA_FINALARRIVAL) {
+ // Santa should have already left, but no data yet, hide countdown and show message
+ stopCountdown();
+ enableTrackerMode(false);
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ } else {
+ // Post Christmas
+ stopCountdown();
+ enableTrackerMode(false);
+ launchSanta.setState(AbstractLaunch.STATE_FINISHED);
+ }
+ }
+
+
+ /**
+ * Move to 'data' (online) state.
+ */
+ private void stateData() {
+ Log.d(TAG, "Santa is online.");
+
+ // Enable/disable pins and nav drawer
+ updateNavigation();
+
+ // Schedule next UI update
+ scheduleUIUpdate();
+
+ long time = SantaPreferences.getCurrentTime();
+
+ AbstractLaunch launchSanta = mCardAdapter.getLauncher(CardAdapter.SANTA);
+ // Is Santa finished?
+ if (time > mFirstDeparture && time < OFFLINE_SANTA_FINALARRIVAL) {
+ // Santa should be travelling, enable map and hide countdown
+ enableTrackerMode(true);
+
+ if (mFlagSwitchOff) {
+ // Kill-switch triggered, disable button
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ } else if (time > mFinalArrival) {
+ // No data
+ launchSanta.setState(AbstractLaunch.STATE_DISABLED);
+ } else {
+ launchSanta.setState(AbstractLaunch.STATE_READY);
+ }
+
+ } else if (time < mFirstDeparture) {
+ // Santa hasn't taken off yet, start count-down and schedule
+ // notification to first departure, hide buttons
+
+ startCountdown(mFirstDeparture);
+ launchSanta.setState(AbstractLaunch.STATE_LOCKED);
+
+ } else {
+ // Post Christmas, hide countdown and buttons
+ launchSanta.setState(AbstractLaunch.STATE_FINISHED);
+ stopCountdown();
+ enableTrackerMode(false);
+ }
+
+ }
+
+ public void enableTrackerMode(boolean showLaunchButton) {
+ mCountdown.cancel();
+ mVillageBackdrop.setImageResource(R.drawable.village_bg_launch);
+ mVillage.setPlaneEnabled(false);
+ mLaunchButton.setVisibility(showLaunchButton ? View.VISIBLE : View.GONE);
+ mCountdownView.setVisibility(View.GONE);
+ }
+
+ public void startCountdown(long time) {
+ mCountdown.startTimer(time - SantaPreferences.getCurrentTime());
+ mVillageBackdrop.setImageResource(R.drawable.village_bg_countdown);
+ mVillage.setPlaneEnabled(true);
+ mLaunchButton.setVisibility(View.GONE);
+ mCountdownView.setVisibility(View.VISIBLE);
+ }
+
+ public void stopCountdown() {
+ mCountdown.cancel();
+ mCountdownView.setVisibility(View.GONE);
+ }
+
+ /*
+ * Village Markers
+ */
+ private void updateNavigation() {
+ // Games
+ mCardAdapter.getLauncher(TvCardAdapter.JETPACK)
+ .setState(getGamePinState(mFlagDisableJetpack, UNLOCK_JETPACK));
+ mCardAdapter.getLauncher(TvCardAdapter.ROCKET).setState(
+ getGamePinState(mFlagDisableRocket, UNLOCK_ROCKET));
+ mCardAdapter.getLauncher(TvCardAdapter.SNOWDOWN).setState(
+ getGamePinState(mFlagDisableSnowdown, UNLOCK_SNOWDOWN));
+
+ ((LaunchVideo) mCardAdapter.getLauncher(TvCardAdapter.VIDEO01)).setVideo(
+ mVideoList[0], UNLOCK_VIDEO_1);
+ ((LaunchVideo) mCardAdapter.getLauncher(TvCardAdapter.VIDEO15)).setVideo(
+ mVideoList[1], UNLOCK_VIDEO_15);
+ ((LaunchVideo) mCardAdapter.getLauncher(TvCardAdapter.VIDEO23)).setVideo(
+ mVideoList[2], UNLOCK_VIDEO_23);
+ }
+
+ private int getGamePinState(boolean disabledFlag, long unlockTime) {
+ if (disabledFlag) {
+ return AbstractLaunch.STATE_HIDDEN;
+ } else if (!disabledFlag && SantaPreferences.getCurrentTime() < unlockTime) {
+ return AbstractLaunch.STATE_LOCKED;
+ } else {
+ return AbstractLaunch.STATE_READY;
+ }
+ }
+
+ /*
+ * Scheduled UI update
+ */
+
+ /**
+ * Schedule a call to {@link #stateData()} or {@link #stateNoData()} at the next time at which
+ * the UI should be updated (games become available, Santa takes off, Santa is finished).
+ */
+ private void scheduleUIUpdate() {
+ // cancel scheduled update
+ cancelUIUpdate();
+
+ final long delay = calculateNextUiUpdateDelay();
+ if (delay > 0 && delay < Long.MAX_VALUE) {
+ // schedule if delay is in the future
+ mHandler.postDelayed(mUpdateUiRunnable, delay);
+ }
+ }
+
+ private long calculateNextUiUpdateDelay() {
+
+ final long time = SantaPreferences.getCurrentTime();
+
+ final long departureDelay = mFirstDeparture - time;
+ final long arrivalDelay = mFinalArrival - time;
+
+ // if disable flag is toggled, exclude from calculation
+ final long[] delays = new long[]{
+ mFlagDisableJetpack ? Long.MAX_VALUE : UNLOCK_JETPACK - time,
+ mFlagDisableRocket ? Long.MAX_VALUE : UNLOCK_ROCKET - time,
+ mFlagDisableSnowdown ? Long.MAX_VALUE : UNLOCK_SNOWDOWN - time,
+ departureDelay, arrivalDelay};
+
+ // find lowest delay, but only count positive values or zero (ie. that are in the future)
+ long delay = Long.MAX_VALUE;
+ for (final long x : delays) {
+ if (x >= 0) {
+ delay = Math.min(delay, x);
+ }
+ }
+
+ return delay;
+ }
+
+ private void cancelUIUpdate() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ private Runnable mUpdateUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mWaitingForApi) {
+ stateData();
+ } else {
+ stateNoData();
+ }
+ }
+ };
+
+ /*
+ * Google Play Services - from
+ * http://code.google.com/p/google-api-java-client/source/browse/tasks-android-sample/src/main/
+ * java/com/google/api/services/samples/tasks/android/TasksSample.java?repo=samples
+ */
+
+ /**
+ * Check that Google Play services APK is installed and up to date.
+ */
+ private boolean checkGooglePlayServicesAvailable() {
+ final int connectionStatusCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ if (GooglePlayServicesUtil.isUserRecoverableError(connectionStatusCode)) {
+ showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
+ return false;
+ }
+ return (connectionStatusCode == ConnectionResult.SUCCESS);
+ }
+
+ private void showGooglePlayServicesAvailabilityErrorDialog(final int connectionStatusCode) {
+
+ Dialog dialog = GooglePlayServicesUtil.getErrorDialog(connectionStatusCode, this, 0);
+ dialog.show();
+ dialog.setOnDismissListener(new Dialog.OnDismissListener() {
+
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+ });
+
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.launch_button:
+ launchTracker();
+ break;
+ }
+ }
+
+ @Override
+ public void playSoundOnce(int resSoundId) {
+ mAudioPlayer.playTrack(resSoundId, false);
+ }
+
+ @Override
+ public Context getActivityContext() {
+ return this;
+ }
+
+ @Override
+ public void launchActivity(Intent intent) {
+ launchActivityInternal(intent, null, 0);
+ }
+
+ @Override
+ public void launchActivityDelayed(final Intent intent, final View v) {
+ launchActivityInternal(intent, v, 200);
+ }
+
+ @Override
+ public View getCountdownView() {
+ return findViewById(R.id.countdown_container);
+ }
+
+ @Override
+ public void onCountdownFinished() {
+ if (!mWaitingForApi) {
+ stateData();
+ } else {
+ stateNoData();
+ }
+ }
+
+ /** Attempt to launch the tracker, if available. */
+ public void launchTracker() {
+ AbstractLaunch launch = mCardAdapter.getLauncher(CardAdapter.SANTA);
+ if (launch instanceof LaunchSanta) {
+ LaunchSanta tracker = (LaunchSanta) launch;
+
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_launch,
+ R.string.analytics_launch_action_village);
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_launch),
+ getString(R.string.analytics_launch_action_village));
+
+ tracker.onClick(mLaunchButton);
+ }
+ }
+
+ /*
+ * Service communication
+ */
+
+ private static class IncomingHandler extends Handler {
+
+ private final WeakReference mActivityRef;
+
+ public IncomingHandler(TvStartupActivity activity) {
+ mActivityRef = new WeakReference<>(activity);
+ }
+
+ /*
+ Order in which messages are received: Data updates, State of Service [Idle, Error]
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ SantaLog.d(TAG, "message=" + msg.what);
+ final TvStartupActivity activity = mActivityRef.get();
+ if (activity == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case SantaServiceMessages.MSG_SERVICE_STATUS:
+ // Current state of service, received once when connecting
+ activity.onSantaServiceStateUpdate(msg.arg1);
+ break;
+ case SantaServiceMessages.MSG_INPROGRESS_UPDATE_ROUTE:
+ // route is about to be updated
+ activity.onRouteUpdateStart();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_ROUTE:
+ // route data has been updated
+ activity.onRouteDataUpdateFinished();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_ONOFF:
+ activity.mFlagSwitchOff = (msg.arg1 == SantaServiceMessages.SWITCH_OFF);
+ activity.onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_TIMES:
+ Bundle b = (Bundle) msg.obj;
+ activity.mOffset = b.getLong(SantaServiceMessages.BUNDLE_OFFSET);
+ SantaPreferences.cacheOffset(activity.mOffset);
+ activity.mFinalArrival = b.getLong(SantaServiceMessages.BUNDLE_FINAL_ARRIVAL);
+ activity.mFirstDeparture = b.getLong(SantaServiceMessages.BUNDLE_FIRST_DEPARTURE);
+ activity.onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_GAMES:
+ final int arg = msg.arg1;
+ activity.mFlagDisableJetpack = (arg & SantaServiceMessages.MSG_FLAG_GAME_JETPACK)
+ == SantaServiceMessages.MSG_FLAG_GAME_JETPACK;
+ activity.mFlagDisableRocket = (arg & SantaServiceMessages.MSG_FLAG_GAME_ROCKET)
+ == SantaServiceMessages.MSG_FLAG_GAME_ROCKET;
+ activity.mFlagDisableSnowdown = (arg & SantaServiceMessages.MSG_FLAG_GAME_SNOWDOWN)
+ == SantaServiceMessages.MSG_FLAG_GAME_SNOWDOWN;
+ activity.onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_UPDATED_VIDEOS:
+ Bundle data = msg.getData();
+ activity.mVideoList = data.getStringArray(SantaServiceMessages.BUNDLE_VIDEOS);
+ activity.onDataUpdate();
+ break;
+ case SantaServiceMessages.MSG_ERROR:
+ // Error accessing the API, ignore because there is data.
+ activity.onApiSuccess();
+ break;
+ case SantaServiceMessages.MSG_ERROR_NODATA:
+ activity.stateNoData();
+ break;
+ case SantaServiceMessages.MSG_SUCCESS:
+ activity.onApiSuccess();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+
+ }
+ }
+
+ /**
+ * Handle the state of the SantaService when first connecting to it.
+ */
+ private void onSantaServiceStateUpdate(int state) {
+ switch (state) {
+ case SantaServiceMessages.STATUS_IDLE:
+ // Service is idle, data should be uptodate
+ mWaitingForApi = false;
+ stateData();
+ break;
+ case SantaServiceMessages.STATUS_IDLE_NODATA:
+ mWaitingForApi = true;
+ stateNoData();
+ break;
+ case SantaServiceMessages.STATUS_ERROR_NODATA:
+ // Service is in error state and there is no valid data
+ mWaitingForApi = true;
+ stateNoData();
+ case SantaServiceMessages.STATUS_ERROR:
+ // Service is in error state and waiting for another attempt to access API
+ mWaitingForApi = true;
+ stateNoData();
+ case SantaServiceMessages.STATUS_PROCESSING:
+ // Service is busy processing an update, wait for success and ignore this state
+ mWaitingForApi = true;
+ break;
+
+ }
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = new Messenger(service);
+
+ //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;
+ }
+ };
+
+
+ private void onApiSuccess() {
+
+ if (mWaitingForApi) {
+ mWaitingForApi = false;
+ stateData();
+ }
+ }
+
+ private void onRouteDataUpdateFinished() {
+ // switch to 'online' mode, data has been loaded
+ if (!mWaitingForApi) {
+ stateData();
+ }
+ }
+
+ private void onRouteUpdateStart() {
+ // temporarily switch back to offline mode until route update has finished
+ if (!mWaitingForApi) {
+ stateNoData();
+ }
+ }
+
+ private void onDataUpdate() {
+ if (!mWaitingForApi) {
+ stateData();
+ }
+ }
+
+ private void registerWithService() {
+ bindService(new Intent(this, SantaService.class), mConnection, Context.BIND_AUTO_CREATE);
+ mIsBound = true;
+ }
+
+ 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
+ }
+ }
+ unbindService(mConnection);
+ mIsBound = false;
+ }
+ }
+
+ private synchronized void launchActivityInternal(
+ final Intent intent, View srcView, long delayMs) {
+
+ if (!mLaunchingChild) {
+ mLaunchingChild = true;
+
+ // stop timer
+ if (mCountdown != null) {
+ mCountdown.cancel();
+ }
+
+ if (srcView != null) {
+ playCircularRevealTransition(srcView);
+ }
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ startActivityForResult(intent, RC_STARTUP);
+ mLaunchingChild = false;
+ }
+ }, delayMs);
+ }
+ }
+
+ final Rect mSrcRect = new Rect();
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void playCircularRevealTransition(View srcView) {
+
+ showColorMask(true);
+ View mask = findViewById(R.id.content_mask);
+ srcView.getGlobalVisibleRect(mSrcRect);
+ Animator anim = ViewAnimationUtils.createCircularReveal(mask,
+ mSrcRect.centerX(), mSrcRect.centerY(), 0.f, mask.getWidth());
+ anim.start();
+ }
+
+ private void showColorMask(boolean show) {
+ int visibility = show ? View.VISIBLE: View.INVISIBLE;
+ (findViewById(R.id.content_mask)).setVisibility(visibility);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/VoiceAction.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/VoiceAction.java
new file mode 100644
index 000000000..3183407f8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/launch/VoiceAction.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 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.content.Intent;
+
+import com.google.android.gms.actions.SearchIntents;
+
+import java.util.Arrays;
+
+/**
+ * Support for Google Now (Voice) Actions
+ * See http://android-developers.blogspot.com/2014/10/the-fastest-route-between-voice-search.html
+ */
+
+public class VoiceAction {
+
+ public static final String ACTION_SHOW_SANTA
+ = "com.google.android.apps.santatracker.SHOW_SANTA";
+ /**
+ * When the system detects ACTION_SHOW_SANTA, the name is passed as this parameter
+ */
+ public static final String ACTION_SHOW_SANTA_EXTRA = "santa";
+
+ public static final String ACTION_PLAY_GAME
+ = "com.google.android.apps.santatracker.PLAY_GAME";
+ /**
+ * When the system detects ACTION_PLAY_GAME_EXTRA, the game name is passed as this parameter
+ */
+ public static final String ACTION_PLAY_GAME_EXTRA = "game";
+
+ /**
+ * Pick one of the available games
+ */
+ public static final String ACTION_PLAY_RANDOM_GAME
+ = "com.google.android.apps.santatracker.PLAY_RANDOM_GAME";
+
+ private static final String[] SUPPORTED_ACTIONS = {SearchIntents.ACTION_SEARCH,
+ ACTION_SHOW_SANTA, ACTION_PLAY_GAME, ACTION_PLAY_RANDOM_GAME};
+
+
+ public static boolean isVoiceAction(Intent intent) {
+ return Arrays.asList(SUPPORTED_ACTIONS).contains(intent.getAction());
+ }
+
+ public interface VoiceActionHandler {
+
+ /**
+ * Callback method for handling voice actions
+ *
+ * @param intent Google Now Actions intent.
+ * @return true if the action was handled or will be handled
+ */
+ boolean handleVoiceAction(Intent intent);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/BottomSheetBehavior.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/BottomSheetBehavior.java
new file mode 100644
index 000000000..6980f7a4e
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/BottomSheetBehavior.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.Context;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.google.android.apps.santatracker.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+
+/**
+ * An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as
+ * a bottom sheet.
+ */
+public class BottomSheetBehavior extends CoordinatorLayout.Behavior {
+
+ /**
+ * Listener for monitoring events about bottom sheets.
+ */
+ public abstract static class BottomSheetListener {
+
+ /**
+ * Called when the bottom sheet changes its state.
+ *
+ * @param newState The new state. This will be one of {@link #STATE_DRAGGING},
+ * {@link #STATE_SETTLING}, {@link #STATE_EXPANDED},
+ * {@link #STATE_COLLAPSED}, or {@link #STATE_HIDDEN}.
+ */
+ public abstract void onStateChanged(@State int newState);
+
+ /**
+ * Called when the bottom sheet is being dragged.
+ *
+ * @param slideOffset The new offset of this bottom sheet within its range, from 0 to 1
+ * when it is moving upward, and from 0 to -1 when it moving downward.
+ */
+ public abstract void onSlide(float slideOffset);
+ }
+
+ /**
+ * The bottom sheet is dragging.
+ */
+ public static final int STATE_DRAGGING = 1;
+
+ /**
+ * The bottom sheet is settling.
+ */
+ public static final int STATE_SETTLING = 2;
+
+ /**
+ * The bottom sheet is expanded.
+ */
+ public static final int STATE_EXPANDED = 3;
+
+ /**
+ * The bottom sheet is collapsed.
+ */
+ public static final int STATE_COLLAPSED = 4;
+
+ /**
+ * The bottom sheet is hidden.
+ */
+ public static final int STATE_HIDDEN = 5;
+
+ /** @hide */
+ @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING, STATE_HIDDEN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
+ private static final float HIDE_THRESHOLD = 0.5f;
+
+ private static final float HIDE_FRICTION = 0.1f;
+
+ // Whether to enable workaround for black non-rendered square
+ private static final boolean NEEDS_INVALIDATING = Build.VERSION.SDK_INT < 23;
+
+ private float mMaximumVelocity;
+
+ private int mPeekHeight;
+
+ private int mHiddenPeekHeight;
+
+ private int mMinOffset;
+
+ private int mMaxOffset;
+
+ private boolean mHideable;
+
+ @State
+ private int mState = STATE_COLLAPSED;
+
+ private ViewDragHelper mViewDragHelper;
+
+ private boolean mIgnoreEvents;
+
+ private int mLastNestedScrollDy;
+
+ private int mParentHeight;
+
+ private WeakReference mViewRef;
+
+ private BottomSheetListener mListener;
+
+ private VelocityTracker mVelocityTracker;
+
+ private int mActivePointerId;
+
+ /**
+ * Default constructor for instantiating BottomSheetBehaviors.
+ */
+ public BottomSheetBehavior() {
+ }
+
+ /**
+ * Default constructor for inflating BottomSheetBehaviors from layout.
+ *
+ * @param context The {@link Context}.
+ * @param attrs The {@link AttributeSet}.
+ */
+ public BottomSheetBehavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.BottomSheetBehavior_Params);
+ setPeekHeight(a.getDimensionPixelSize(
+ R.styleable.BottomSheetBehavior_Params_behavior_peekHeight, 0));
+ setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Params_behavior_hideable, false));
+ setHiddenPeekHeight(a.getDimensionPixelSize(
+ R.styleable.BottomSheetBehavior_Params_behavior_hiddenPeekHeight, 0));
+ a.recycle();
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
+ return new SavedState(super.onSaveInstanceState(parent, child), mState);
+ }
+
+ @Override
+ public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(parent, child, ss.getSuperState());
+ // Intermediate states are restored as collapsed state
+ if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) {
+ mState = STATE_COLLAPSED;
+ } else {
+ mState = ss.state;
+ }
+ }
+
+ @Override
+ public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
+ // First let the parent lay it out
+ if (mState != STATE_DRAGGING && mState != STATE_SETTLING) {
+ parent.onLayoutChild(child, layoutDirection);
+ }
+ // Offset the bottom sheet
+ mParentHeight = parent.getHeight();
+ mMinOffset = Math.max(0, mParentHeight - child.getHeight());
+ mMaxOffset = mParentHeight - mPeekHeight;
+ if (mState == STATE_EXPANDED) {
+ ViewCompat.offsetTopAndBottom(child, mMinOffset);
+ } else if (mHideable && mState == STATE_HIDDEN) {
+ ViewCompat.offsetTopAndBottom(child, mParentHeight - mHiddenPeekHeight);
+ } else if (mState == STATE_COLLAPSED) {
+ ViewCompat.offsetTopAndBottom(child, mMaxOffset);
+ }
+ if (mViewDragHelper == null) {
+ mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
+ }
+ mViewRef = new WeakReference<>(child);
+ return true;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+ int action = MotionEventCompat.getActionMasked(event);
+ // Record the velocity
+ if (action == MotionEvent.ACTION_DOWN) {
+ reset();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ // Reset the ignore flag
+ if (mIgnoreEvents) {
+ mIgnoreEvents = false;
+ return false;
+ }
+ break;
+ case MotionEvent.ACTION_DOWN:
+ mIgnoreEvents = !parent.isPointInChildBounds(child,
+ (int) event.getX(), (int) event.getY());
+ mActivePointerId = MotionEventCompat.getPointerId(event, 0);
+ break;
+ }
+ return !mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+ mViewDragHelper.processTouchEvent(event);
+ // Record the velocity
+ if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ reset();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ return true;
+ }
+
+ @Override
+ public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child,
+ View directTargetChild, View target, int nestedScrollAxes) {
+ mLastNestedScrollDy = 0;
+ return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+ }
+
+ @Override
+ public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx,
+ int dy, int[] consumed) {
+ int currentTop = child.getTop();
+ int newTop = currentTop - dy;
+ if (dy > 0) { // Upward
+ if (newTop < mMinOffset) {
+ consumed[1] = currentTop - mMinOffset;
+ child.offsetTopAndBottom(-consumed[1]);
+ if (NEEDS_INVALIDATING && mState != STATE_EXPANDED) {
+ child.invalidate();
+ }
+ setStateInternal(STATE_EXPANDED);
+ } else {
+ consumed[1] = dy;
+ child.offsetTopAndBottom(-dy);
+ setStateInternal(STATE_DRAGGING);
+ if (NEEDS_INVALIDATING) {
+ child.invalidate();
+ }
+ }
+ } else if (dy < 0) { // Downward
+ if (!ViewCompat.canScrollVertically(target, -1)) {
+ if (newTop <= mMaxOffset) {
+ consumed[1] = dy;
+ child.offsetTopAndBottom(-dy);
+ setStateInternal(STATE_DRAGGING);
+ if (NEEDS_INVALIDATING) {
+ coordinatorLayout.invalidate(child.getLeft(), currentTop,
+ child.getRight(), coordinatorLayout.getHeight());
+ }
+ } else if (mHideable) {
+ if (newTop <= mParentHeight - mHiddenPeekHeight) {
+ consumed[1] = dy;
+ child.offsetTopAndBottom(-dy);
+ setStateInternal(STATE_DRAGGING);
+ } else {
+ consumed[1] = currentTop - (mParentHeight - mHiddenPeekHeight);
+ child.offsetTopAndBottom(-consumed[1]);
+ setStateInternal(STATE_HIDDEN);
+ }
+ if (NEEDS_INVALIDATING) {
+ coordinatorLayout.invalidate(child.getLeft(), currentTop,
+ child.getRight(), coordinatorLayout.getHeight());
+ }
+ } else {
+ consumed[1] = currentTop - mMaxOffset;
+ child.offsetTopAndBottom(-consumed[1]);
+ setStateInternal(STATE_COLLAPSED);
+ }
+ }
+ }
+ dispatchOnSlide(child.getTop());
+ mLastNestedScrollDy = dy;
+ }
+
+ @Override
+ public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
+ if (child.getTop() == mMinOffset) {
+ return;
+ }
+ int top;
+ int targetState;
+ if (mLastNestedScrollDy > 0) { // Upward
+ if (child.getTop() > mMaxOffset) {
+ top = mMaxOffset;
+ targetState = STATE_COLLAPSED;
+ } else {
+ top = mMinOffset;
+ targetState = STATE_EXPANDED;
+ }
+ } else if (mHideable && shouldHide(child, getYVelocity())) { // Downward (hide)
+ top = mParentHeight - mHiddenPeekHeight;
+ targetState = STATE_HIDDEN;
+ } else {
+ top = mMaxOffset;
+ targetState = STATE_COLLAPSED;
+ }
+ if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
+ setStateInternal(STATE_SETTLING);
+ ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
+ } else {
+ setStateInternal(targetState);
+ }
+ }
+
+ @Override
+ public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
+ float velocityX, float velocityY) {
+ return mState != STATE_EXPANDED ||
+ super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
+ }
+
+ /**
+ * Sets the height of the bottom sheet when it is collapsed.
+ *
+ * @param peekHeight The height of the collapsed bottom sheet in pixels.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight
+ */
+ public final void setPeekHeight(int peekHeight) {
+ mPeekHeight = Math.max(0, peekHeight);
+ mMaxOffset = mParentHeight - peekHeight;
+ }
+
+ /**
+ * Gets the height of the bottom sheet when it is collapsed.
+ *
+ * @return The height of the collapsed bottom sheet.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight
+ */
+ public final int getPeekHeight() {
+ return mPeekHeight;
+ }
+
+ /**
+ * Sets whether this bottom sheet can hide when it is swiped down.
+ *
+ * @param hideable {@code true} to make this bottom sheet hideable.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_hideable
+ */
+ public void setHideable(boolean hideable) {
+ mHideable = hideable;
+ }
+
+ /**
+ * Gets whether this bottom sheet can hide when it is swiped down.
+ *
+ * @return {@code true} if this bottom sheet can hide.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_hideable
+ */
+ public boolean isHideable() {
+ return mHideable;
+ }
+
+ public void setHiddenPeekHeight(int hiddenPeekHeight) {
+ mHiddenPeekHeight = hiddenPeekHeight;
+ }
+
+ public int getHiddenPeekHeight() {
+ return mHiddenPeekHeight;
+ }
+
+ /**
+ * Sets a listener to be notified of bottom sheet events.
+ *
+ * @param listener The listener to notify when bottom sheet events occur.
+ */
+ public void setBottomSheetListener(BottomSheetListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Sets the state of the bottom sheet. The bottom sheet will transition to that state with
+ * animation.
+ *
+ * @param state One of {@link #STATE_COLLAPSED}, {@link #STATE_EXPANDED}, or
+ * {@link #STATE_HIDDEN}.
+ */
+ public final void setState(@State int state) {
+ V child = mViewRef.get();
+ if (child == null) {
+ return;
+ }
+ int top;
+ if (state == STATE_COLLAPSED) {
+ top = mMaxOffset;
+ } else if (state == STATE_EXPANDED) {
+ top = mMinOffset;
+ } else if (mHideable && state == STATE_HIDDEN) {
+ top = mParentHeight - mHiddenPeekHeight;
+ } else {
+ throw new IllegalArgumentException("Illegal state argument: " + state);
+ }
+ setStateInternal(STATE_SETTLING);
+ if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
+ ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));
+ }
+ }
+
+ /**
+ * Gets the current state of the bottom sheet.
+ *
+ * @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING},
+ * and {@link #STATE_SETTLING}.
+ */
+ @State
+ public final int getState() {
+ return mState;
+ }
+
+ private void setStateInternal(@State int state) {
+ if (mState == state) {
+ return;
+ }
+ mState = state;
+ if (mListener != null) {
+ mListener.onStateChanged(state);
+ }
+ }
+
+ private void reset() {
+ mActivePointerId = ViewDragHelper.INVALID_POINTER;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private boolean shouldHide(View child, float yvel) {
+ if (child.getTop() < mMaxOffset) {
+ // It should not hide, but collapse.
+ return false;
+ }
+ final float newTop = child.getTop() + yvel * HIDE_FRICTION;
+ return Math.abs(newTop - mMaxOffset) / (float) mPeekHeight > HIDE_THRESHOLD;
+ }
+
+ private float getYVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ return VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId);
+ }
+
+ private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
+
+ @Override
+ public boolean tryCaptureView(View child, int pointerId) {
+ return mViewRef != null && mViewRef.get() == child;
+ }
+
+ @Override
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ dispatchOnSlide(top);
+ if (NEEDS_INVALIDATING) {
+ if (dy < 0) { // Upward
+ changedView.invalidate();
+ } else { // Downward
+ ViewParent parent = changedView.getParent();
+ if (parent instanceof View) {
+ View v = (View) parent;
+ v.invalidate(changedView.getLeft(), top - dy,
+ changedView.getRight(), v.getHeight());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onViewDragStateChanged(int state) {
+ if (state == ViewDragHelper.STATE_DRAGGING) {
+ setStateInternal(STATE_DRAGGING);
+ }
+ }
+
+ @Override
+ public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ int top;
+ @State int targetState;
+ if (yvel < 0) { // Moving up
+ top = mMinOffset;
+ targetState = STATE_EXPANDED;
+ } else if (mHideable && shouldHide(releasedChild, yvel)) {
+ top = mParentHeight - mHiddenPeekHeight;
+ targetState = STATE_HIDDEN;
+ } else {
+ top = mMaxOffset;
+ targetState = STATE_COLLAPSED;
+ }
+ if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) {
+ setStateInternal(STATE_SETTLING);
+ ViewCompat.postOnAnimation(releasedChild,
+ new SettleRunnable(releasedChild, targetState));
+ } else {
+ setStateInternal(targetState);
+ }
+ }
+
+ @Override
+ public int clampViewPositionVertical(View child, int top, int dy) {
+ return constrain(top, mMinOffset, mHideable ? mParentHeight - mHiddenPeekHeight : mMaxOffset);
+ }
+
+ @Override
+ public int clampViewPositionHorizontal(View child, int left, int dx) {
+ return child.getLeft();
+ }
+
+ };
+
+ private void dispatchOnSlide(int top) {
+ if (mListener != null) {
+ if (top > mMaxOffset) {
+ mListener.onSlide((float) (mMaxOffset - top) / mPeekHeight);
+ } else {
+ mListener.onSlide((float) (mMaxOffset - top) / ((mMaxOffset - mMinOffset)));
+ }
+ }
+ }
+
+ private class SettleRunnable implements Runnable {
+
+ private final View mView;
+
+ @State
+ private final int mTargetState;
+
+ SettleRunnable(View view, @State int targetState) {
+ mView = view;
+ mTargetState = targetState;
+ if (NEEDS_INVALIDATING) {
+ // We need to invalidate the parent here, or the following animation won't be drawn.
+ ViewParent parent = mView.getParent();
+ if (parent instanceof View) {
+ ((View) parent).invalidate();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
+ ViewCompat.postOnAnimation(mView, this);
+ } else {
+ setStateInternal(mTargetState);
+ }
+ }
+ }
+
+ protected static class SavedState extends View.BaseSavedState {
+
+ @State
+ final int state;
+
+ public SavedState(Parcel source) {
+ super(source);
+ //noinspection ResourceType
+ state = source.readInt();
+ }
+
+ public SavedState(Parcelable superState, @State int state) {
+ super(superState);
+ this.state = state;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(state);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public SavedState createFromParcel(Parcel source) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * A utility function to get the {@link BottomSheetBehavior} associated with the {@code view}.
+ *
+ * @param view The {@link View} with {@link BottomSheetBehavior}.
+ * @return The {@link BottomSheetBehavior} associated with the {@code view}.
+ */
+ @SuppressWarnings("unchecked")
+ public static BottomSheetBehavior from(V view) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ if (!(params instanceof CoordinatorLayout.LayoutParams)) {
+ throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
+ }
+ CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
+ .getBehavior();
+ if (!(behavior instanceof BottomSheetBehavior)) {
+ throw new IllegalArgumentException(
+ "The view is not associated with BottomSheetBehavior");
+ }
+ return (BottomSheetBehavior) behavior;
+ }
+
+ private static int constrain(int amount, int low, int high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/CircleCutout.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/CircleCutout.java
new file mode 100644
index 000000000..591ddb78f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/CircleCutout.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Created by jfschmakeit on 13/11/2013.
+ */
+public class CircleCutout extends View {
+
+ public CircleCutout(Context context) {
+ super(context);
+ }
+
+ public CircleCutout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CircleCutout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ if (oldw != w || oldh != h) {
+ // Initialise new bitmap
+
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(Color.TRANSPARENT);
+
+ Paint eraser = new Paint();
+ eraser.setColor(0xFFFFFFFF);
+ eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ canvas.drawColor(Color.parseColor("#559A82AD"));
+ Paint p = new Paint();
+ p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+// Paint p = new Paint();
+ p.setColor(Color.argb(0, 0, 0, 0));
+ canvas.drawCircle(canvas.getWidth() / 2f, canvas.getHeight() / 2f,
+ Math.min(canvas.getHeight() / 2f, canvas.getWidth() / 2f), eraser);
+
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/DestinationInfoWindowAdapter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/DestinationInfoWindowAdapter.java
new file mode 100644
index 000000000..0ae62cfb6
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/DestinationInfoWindowAdapter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 com.google.android.apps.santatracker.R;
+import com.google.android.gms.maps.GoogleMap.InfoWindowAdapter;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.apps.santatracker.data.Destination;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * {@link InfoWindowAdapter} for Destinations.
+ *
+ * @author jfschmakeit
+ */
+public class DestinationInfoWindowAdapter implements InfoWindowAdapter {
+
+ //private static final String TAG = "DestinationInfoAdapter";
+ private final TextView mTitle;
+ private final View mWindow;
+
+ private Destination mDestination = null;
+
+ private DestinationInfoWindowInterface mCallback;
+
+ public DestinationInfoWindowAdapter(LayoutInflater inflater,
+ DestinationInfoWindowInterface callback, Context c) {
+ mWindow = inflater.inflate(R.layout.infowindow, null);
+
+ mTitle = (TextView) mWindow.findViewById(R.id.info_title);
+ this.mCallback = callback;
+
+ // TypeFaces: label,title=roboto-condensed, content=roboto-light
+ final Typeface robotoCondensed = Typeface.createFromAsset(c.getAssets(),
+ c.getResources().getString(R.string.typeface_robotocondensed_regular));
+ final Typeface robotoLight = Typeface.createFromAsset(c.getAssets(),
+ c.getResources().getString(R.string.typeface_roboto_light));
+
+ mTitle.setTypeface(robotoCondensed);
+ }
+
+ public void setData(Destination destination) {
+ mDestination = destination;
+ }
+
+ public View getInfoWindow(Marker marker) {
+ // name
+ mTitle.setText(mDestination.getPrintName());
+ mTitle.setContentDescription(mTitle.getText());
+
+ return mWindow;
+ }
+
+ public View getInfoContents(Marker marker) {
+ return null;
+ }
+
+ public interface DestinationInfoWindowInterface {
+
+ public Destination getDestinationInfo(int id);
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/PresentMarker.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/PresentMarker.java
new file mode 100644
index 000000000..d63c1e8d1
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/PresentMarker.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 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 com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.Projection;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+import android.graphics.Point;
+import android.os.Handler;
+
+public class PresentMarker {
+
+ public static final String MARKER_TITLE = "PresentMarker";
+
+ private Marker[] mAnimationMarkers;
+ private Marker mMovementMarker;
+ private int mIndex = 0;
+ private GoogleMap mMap;
+ private SantaMarker mSantaMarker;
+ private int mSizeX, mSizeY;
+
+ private LatLng mDestination = null;
+ private int mFrame = 0;
+ private double mDirectionLat, mDirectionLng;
+ public static final int ANIMATION_FRAMES_FADEOUT = 4; // per marker
+ public static final int ANIMATION_FRAMES_MOVING_MAX = 275;
+ public static final int ANIMATION_FRAMES_MOVING_MIN = 175;
+ public static final int ANIMATION_FRAMES_WAIT = 500;
+ private int mTotalAnimationLength;
+ //private static final String TAG = "PresentMarker";
+ private static final double MAXIMUM_ZOOM_LEVEL = 8.7f;
+
+ private LatLng mLocation;
+ private int mAnimationDuration;
+ private Projection mProjection;
+ private boolean mWaitingForProjection = false;
+ private LatLng mSantaPosition;
+ private Handler mHandler;
+
+ private static boolean VALID_CAMERA;
+
+ public PresentMarker(GoogleMap map, SantaMarker santa, Handler handler,
+ int[] animIcons, int screenWidth, int screenHeight) {
+ this.mMap = map;
+ this.mSantaMarker = santa;
+ this.mHandler = handler;
+
+ // setup markers, one per icon
+ mAnimationMarkers = new Marker[animIcons.length - 1];
+ LatLng position = new LatLng(0f, 0f);
+ for (int i = 1; i < animIcons.length; i++) {
+ mAnimationMarkers[i - 1] = mMap.addMarker(new MarkerOptions()
+ .title(MARKER_TITLE)
+ .icon(BitmapDescriptorFactory.fromResource(animIcons[i]))
+ .position(position).visible(false));
+ mAnimationMarkers[i - 1].setVisible(false);
+ }
+ mMovementMarker = mMap.addMarker(new MarkerOptions()
+ .title(MARKER_TITLE)
+ .icon(BitmapDescriptorFactory.fromResource(animIcons[0]))
+ .position(position).visible(false));
+ mMovementMarker.setVisible(false);
+
+ mSizeX = screenWidth;
+ mSizeY = screenHeight;
+
+ // Wait before start
+ mFrame = SantaPreferences.getRandom(-ANIMATION_FRAMES_WAIT, 0);
+
+ reset();
+ }
+
+ public void setProjection(Projection p, LatLng santaPosition) {
+ this.mProjection = p;
+ this.mSantaPosition = santaPosition;
+ this.mWaitingForProjection = false;
+ }
+
+ public static void setViewParameters(double zoom, boolean inSantaCam) {
+ VALID_CAMERA = zoom > MAXIMUM_ZOOM_LEVEL || inSantaCam;
+
+ }
+
+ public void draw() {
+
+ // 5 States: waiting for valid camera for new present location, waiting
+ // for start,
+ // New present, moving, animating/disappearing
+ if (!VALID_CAMERA && (mDestination == null && mProjection == null)) {
+
+ } else if (mAnimationDuration < 0 || mWaitingForProjection) {
+ // wait to start and until projection has been set
+
+ // need to initialise the projection
+ } else if (VALID_CAMERA && mDestination == null && mProjection == null) {
+ // Log.d(TAG,"getting projection - zoom: "+ZOOM_LEVEL);
+ mWaitingForProjection = true;
+ mHandler.post(mGetProjectionRunnable);
+
+ } else if (mDestination == null && mProjection != null) {
+ // pick a new destination from screen coordinates
+ int y = SantaPreferences.getRandom(0, mSizeY);
+ int x = SantaPreferences.getRandom(0, mSizeX);
+
+ mDestination = mProjection.fromScreenLocation(new Point(x, y));
+ if (mDestination == null) {
+ SantaLog.d("SantaPresents", "Point = " + new Point(x, y));
+ }
+
+ mAnimationDuration = SantaPreferences.getRandom(
+ ANIMATION_FRAMES_MOVING_MIN, ANIMATION_FRAMES_MOVING_MAX);
+ mTotalAnimationLength = mAnimationDuration
+ + (ANIMATION_FRAMES_FADEOUT * mAnimationMarkers.length);
+ // calculate speed
+ mDirectionLat = (mDestination.latitude - mSantaPosition.latitude)
+ / mAnimationDuration;
+ mDirectionLng = (mDestination.longitude - mSantaPosition.longitude)
+ / mAnimationDuration;
+ mLocation = mSantaPosition;
+ mHandler.post(mSetVisibleLocationRunnable);
+
+ mFrame = 0;
+ // Log.d(TAG,
+ // "New present Marker position: "+mLocation+" movement: "+mDirectionLat+", "+mDirectionLng);
+ mProjection = null;
+
+ } else if (mFrame < mAnimationDuration && mDestination != null) {
+ // Moving animation
+
+ mLocation = new LatLng(mLocation.latitude + mDirectionLat,
+ mLocation.longitude + mDirectionLng);
+ mHandler.post(mSetLocationRunnable);
+
+ // animate out if frames left for all animation markers
+ } else if (mFrame >= mAnimationDuration
+ && mFrame <= mTotalAnimationLength) {
+
+ if ((mFrame - mAnimationDuration) % ANIMATION_FRAMES_FADEOUT == 0) {
+ // switch to the next marker
+ mHandler.post(mSwapIconRunnable);
+
+ }
+
+ } else if (mFrame > mTotalAnimationLength) {
+ // animation finished, reset and start again after wait
+ mDestination = null;
+ mFrame = SantaPreferences
+ .getRandom(-ANIMATION_FRAMES_MOVING_MAX, 0);
+ }
+
+ // Wait
+ if (!mWaitingForProjection) {
+ mFrame++;
+ }
+ }
+
+ /**
+ * Hides the previous animation marker and marks the given marker visible.
+ * If this is is the first marker, only it will be set visible. If this is
+ * not a marker, nothing will be done.
+ */
+ private void showAnimationMarker(int i) {
+ if (i >= 0 && i < mAnimationMarkers.length) {
+ mAnimationMarkers[i].setPosition(mLocation);
+ mAnimationMarkers[i].setVisible(true);
+ }
+
+ // hide the previous marker
+ if (i - 1 < 0) {
+ mMovementMarker.setVisible(false);
+ } else if (i - 1 < mAnimationMarkers.length) {
+ mAnimationMarkers[i - 1].setVisible(false);
+ }
+
+ }
+
+ public void reset() {
+ mAnimationMarkers[mIndex].setVisible(false);
+ mIndex = 0;
+ }
+
+ public void hide() {
+ mAnimationMarkers[mIndex].setVisible(false);
+ }
+
+ private Runnable mGetProjectionRunnable = new Runnable() {
+
+ public void run() {
+ setProjection(mMap.getProjection(), mSantaMarker.getPosition());
+ }
+ };
+
+ private Runnable mSwapIconRunnable = new Runnable() {
+ public void run() {
+ showAnimationMarker((mFrame - mAnimationDuration)
+ / ANIMATION_FRAMES_FADEOUT);
+ }
+ };
+
+ private Runnable mSetVisibleLocationRunnable = new Runnable() {
+ public void run() {
+ mMovementMarker.setPosition(mLocation);
+ mMovementMarker.setVisible(true);
+ }
+ };
+
+ private Runnable mSetLocationRunnable = new Runnable() {
+ public void run() {
+ mMovementMarker.setPosition(mLocation);
+ }
+ };
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamButton.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamButton.java
new file mode 100644
index 000000000..14bc4173a
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamButton.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.design.widget.FloatingActionButton;
+import android.util.AttributeSet;
+
+import com.google.android.apps.santatracker.R;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SantaCamButton extends FloatingActionButton {
+
+ private final Map mFlashDrawables = new HashMap<>();
+ private final Paint mPaint = new Paint();
+ private final int mIconSize;
+
+ public SantaCamButton(Context context) {
+ this(context, null);
+ }
+
+ public SantaCamButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SantaCamButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.fab_icon_size);
+ mPaint.setColor(Color.WHITE);
+ mPaint.setTextSize(mIconSize);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ }
+
+ /**
+ * Show the specified message for the duration of time.
+ *
+ * @param message The message to show (needs to be very short to fit in the FAB)
+ * @param duration The duration in milliseconds
+ */
+ public void showMessage(String message, long duration) {
+ final Drawable current = getDrawable();
+ setImageDrawable(getFlashDrawable(message));
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ setImageDrawable(current);
+ }
+ }, duration);
+ }
+
+ private Drawable getFlashDrawable(String message) {
+ if (mFlashDrawables.containsKey(message)) {
+ return mFlashDrawables.get(message);
+ }
+ Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_4444);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawText(message, mIconSize / 2,
+ (mIconSize - mPaint.descent() - mPaint.ascent()) / 2, mPaint);
+ Drawable d = new BitmapDrawable(getResources(), bitmap);
+ mFlashDrawables.put(message, d);
+ return d;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamTimeout.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamTimeout.java
new file mode 100644
index 000000000..ba62e0211
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaCamTimeout.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 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 com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/**
+ * Automatically re-enable SantaCam after a timeout has expired.
+ */
+public class SantaCamTimeout {
+
+ private SantaMapFragment mMap;
+
+ // timestamp at which SC is to be reenabled without user interaction
+ private long mCamReEnableTime;
+ private int mCamTimeout = -1;
+
+ // ms to wait without activity before enabling SC again
+ private static final int SANTACAM_AUTO_ENABLE_TIMEOUT = 30000; // 30s
+
+ // s timeout countdown
+ private static final int SANTACAM_AUTO_ENABLE_COUNTDOWN = 5;
+
+ private SantaCamButton mSantaCamButton;
+
+ public SantaCamTimeout(SantaMapFragment map, SantaCamButton santaCamButton) {
+ mMap = map;
+ mSantaCamButton = santaCamButton;
+ }
+
+ public void check() {
+ if (mCamTimeout > 0) {
+ mCamTimeout--;
+ if (mSantaCamButton != null) {
+ mSantaCamButton.showMessage(String.valueOf(mCamTimeout), 500);
+ }
+ } else if (mCamTimeout == 0) {
+ mMap.enableSantaCam(true);
+ mCamTimeout--;
+
+ // App Measurement
+ FirebaseAnalytics measurement = FirebaseAnalytics.getInstance(mMap.getContext());
+ MeasurementManager.recordCustomEvent(measurement,
+ mMap.getString(R.string.analytics_event_category_tracker),
+ mMap.getString(R.string.analytics_tracker_action_cam),
+ mMap.getString(R.string.analytics_tracker_cam_timeout));
+
+ // [ANALYTICS EVENT]: SantaCamEnabled Timeout
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
+ R.string.analytics_tracker_action_cam,
+ R.string.analytics_tracker_cam_timeout);
+ } else if (mCamReEnableTime > 0
+ && System.currentTimeMillis() >= mCamReEnableTime) {
+ mCamTimeout = SANTACAM_AUTO_ENABLE_COUNTDOWN;
+ }
+
+ }
+
+ public void cancel() {
+ mCamReEnableTime = -1;
+ }
+
+ public void reset() {
+ mCamReEnableTime = System.currentTimeMillis() + SANTACAM_AUTO_ENABLE_TIMEOUT;
+ // Log.d(TAG, "Cam interaction - timeout set to :" + mCamReEnableTime
+ // + ", current time: " + System.currentTimeMillis());
+ mCamTimeout = -1;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapActivity.java
new file mode 100644
index 000000000..40b9fa3bc
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapActivity.java
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (C) 2015 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.design.widget.CoordinatorLayout;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.ImageButton;
+import android.widget.Toast;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.SantaApplication;
+import com.google.android.apps.santatracker.cast.NotificationDataCastManager;
+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.SeparatorDecoration;
+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.firebase.analytics.FirebaseAnalytics;
+
+import java.lang.ref.WeakReference;
+
+
+/**
+ * Map Activity that shows Santa's destinations and his path on and after
+ * Christmas.
+ */
+public class SantaMapActivity extends AppCompatActivity implements
+ SantaMapFragment.SantaMapInterface {
+
+ private static String ARRIVING_IN, DEPARTING_IN, NO_NEXT_DESTINATION, CURRENT_LOCATION,
+ NEXT_LOCATION;
+
+ // countdown update frequency (in ms)
+ private static final int DESTINATION_COUNTDOWN_UPDATEINTERVAL = 1000;
+ // countdown is shown every 10 seconds
+ private static final int DESTINATION_COUNTDOWN_DISPLAY_INTERVAL = 1000 * 10;
+
+ // 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;
+
+ // time to allow the screen to stay active
+ private static final long SCREEN_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5m
+
+ protected static final String TAG = "SantaActivity";
+
+ private static final int LOADER_DESTINATIONS = 1;
+ private static final int LOADER_STREAM = 2;
+
+ private CountDownTimer mTimer;
+ private PresentCounter mPresents = new PresentCounter();
+ private SantaCamTimeout mSantaCamTimeout;
+ protected DestinationCursor mDestinations;
+
+ private Handler mScreenLock = new Handler();
+ private Runnable mScreenUnlock = new Runnable() {
+ @Override
+ public void run() {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ };
+
+ // 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;
+
+ 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;
+ protected boolean mFlagDisableCast = true;
+
+ // 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 RecyclerView mRecyclerView;
+
+ // Support for StreetView intent on device
+ private boolean mSupportStreetView = false;
+
+ private AccessibilityManager mAccessibilityManager;
+
+ private SantaCamButton mSantaCamButton;
+ private BottomSheetBehavior mBottomSheetBehavior;
+
+ private NotificationDataCastManager mCastManager;
+
+ private FirebaseAnalytics mMeasurement;
+ private LinearLayoutManager mLayoutManager;
+
+ private ImageButton mButtonTop;
+
+ @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);
+
+ // Needs to be called before setting the content view
+ supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+
+ setContentView(R.layout.activity_map);
+
+ // Set up timer to remove screen lock
+ resetScreenTimer();
+
+ mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ Resources resources = getResources();
+ if (actionBar != null) {
+ // set visibility flags *AFTER* values have been set,
+ // otherwise nothing is displayed on Galaxy devices
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(false);
+ }
+
+ 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 all fragments
+ mMapFragment = (SantaMapFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_map);
+
+ mButtonTop = (ImageButton) findViewById(R.id.top);
+ mButtonTop.setOnClickListener(mOnClickListener);
+ mRecyclerView = (RecyclerView) findViewById(R.id.stream);
+ mSupportStreetView = Intents.canHandleStreetView(this);
+ mRecyclerView.setHasFixedSize(true);
+ mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.addItemDecoration(new SeparatorDecoration(this));
+ mRecyclerView.addOnScrollListener(mOnScrollListener);
+ mAdapter = new CardAdapter(getApplicationContext(), mCardAdapterListener);
+ mAdapter.setHasStableIds(true);
+ mRecyclerView.setAdapter(mAdapter);
+
+ if(NotificationDataCastManager.checkGooglePlayServices(this)){
+ mCastManager = SantaApplication.getCastManager(this);
+ }
+
+ // Santacam button
+ mSantaCamButton = (SantaCamButton) findViewById(R.id.santacam);
+ mSantaCamButton.setOnClickListener(mOnClickListener);
+ if (mMapFragment.isInSantaCam()) {
+ mSantaCamButton.setVisibility(View.GONE);
+ }
+
+ View bottomSheet = findViewById(R.id.bottom_sheet);
+ if (bottomSheet != null) {
+ mBottomSheetBehavior = (BottomSheetBehavior) ((CoordinatorLayout.LayoutParams)
+ bottomSheet.getLayoutParams()).getBehavior();
+ mBottomSheetBehavior.setBottomSheetListener(mBottomSheetListener);
+ }
+
+ findViewById(R.id.main_touchinterceptor).setOnTouchListener(mInterceptorListener);
+ }
+
+ private void initialiseOnChristmas() {
+ mSantaCamTimeout = new SantaCamTimeout(mMapFragment, mSantaCamButton);
+ }
+
+ private OnTouchListener mInterceptorListener = new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ mMapFragment.disableSantaCam();
+ notifyCamInteraction();
+ return false; // propagate touch event to map
+ }
+ };
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mResumed && hasFocus) {
+ mMapFragment.resumeAudio();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mResumed = true;
+
+ resetScreenTimer();
+
+ if (mCastManager == null && NotificationDataCastManager.checkGooglePlayServices(this)){
+ mCastManager = SantaApplication.getCastManager(this);
+ }
+
+ if (mCastManager != null){
+ mCastManager.incrementUiCounter();
+ }
+
+ if (mBottomSheetBehavior != null) {
+ adjustMapPaddings(mBottomSheetBehavior.getState());
+ }
+
+ }
+
+ @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;
+ }
+
+ cancelScreenTimer();
+
+ // stop santa cam
+ onSantacamStateChange(false);
+
+ if(mCastManager != null){
+ mCastManager.decrementUiCounter();
+ }
+
+ // Reset state
+ mHasDataLoaded = false;
+ mIsLive = false;
+ mDestinations = null;
+ mStream = null;
+
+ super.onPause();
+ }
+
+ @Override
+ public void onUserInteraction(){
+ resetScreenTimer();
+ }
+
+ private void resetScreenTimer() {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mScreenLock.removeCallbacks(mScreenUnlock);
+ mScreenLock.postDelayed(mScreenUnlock, SCREEN_IDLE_TIMEOUT_MS);
+ }
+
+ private void cancelScreenTimer() {
+ mScreenLock.removeCallbacks(mScreenUnlock);
+ }
+
+ 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;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_map, menu);
+
+ // Add cast button
+ if (mCastManager != null) {
+ mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
+ }
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ private LoaderManager.LoaderCallbacks mLoaderCallbacks
+ = new LoaderManager.LoaderCallbacks() {
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_DESTINATIONS: {
+ return new AllDestinationCursorLoader(SantaMapActivity.this);
+ }
+ case LOADER_STREAM: {
+ return new StreamCursorLoader(getApplicationContext(), false);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader 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 loader) {
+ switch (loader.getId()) {
+ case LOADER_DESTINATIONS:
+ mDestinations = null;
+ break;
+ case LOADER_STREAM:
+ mStream = null;
+ break;
+ }
+ }
+ };
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ returnToStartupActivity();
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Close the bottom sheet if it is open.
+ if (mBottomSheetBehavior != null &&
+ mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
+ if (mRecyclerView != null) {
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+ mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ /**
+ * 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, mSupportStreetView);
+ 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");
+ initialiseOnChristmas();
+ 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, mSupportStreetView);
+ // 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, mSupportStreetView);
+ 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, false);
+ // 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),
+ mRecyclerView, mAccessibilityManager);
+
+ // cancel the countdown if it is already running
+ if (mTimer != null) {
+ mTimer.cancel();
+ }
+ mTimer = new CountDownTimer(nextDestination.arrival - SantaPreferences.getCurrentTime(),
+ DESTINATION_COUNTDOWN_UPDATEINTERVAL) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ countdownTick(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ // reached destination - visit destination
+ visitDestination(nextDestination, true);
+ }
+ };
+ if (mResumed) {
+ mTimer.start();
+ }
+ }
+
+ private DashboardViewHolder getDashboardViewHolder() {
+ return (DashboardViewHolder) mRecyclerView.findViewHolderForItemId(mAdapter.getDashboardId());
+ }
+
+ 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, boolean showStreetView) {
+ mAdapter.addDestination(false, next, showStreetView);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * 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()),
+ mRecyclerView, 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_UPDATEINTERVAL) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ countdownTick(millisUntilFinished);
+ }
+
+ @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 setCountdown(String countdown) {
+ DashboardViewHolder holder = getDashboardViewHolder();
+ if (holder == null) {
+ return;
+ }
+ holder.countdown.setText(countdown);
+ }
+
+ 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 countdownTick(long millisUntilFinished) {
+ final long presents = mPresents
+ .getPresents(SantaPreferences.getCurrentTime());
+ final String presentsString = DashboardFormats.formatPresents(presents);
+ setPresentsDelivered(presentsString);
+ final DashboardViewHolder holder = getDashboardViewHolder();
+ if (holder != null) {
+ if ((millisUntilFinished / DESTINATION_COUNTDOWN_DISPLAY_INTERVAL) % 2 == 1) {
+ if (holder.presentsContainer.getVisibility() != View.VISIBLE) {
+ holder.presentsContainer.setVisibility(View.VISIBLE);
+ holder.countdownContainer.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ setCountdown(DashboardFormats.formatCountdown(millisUntilFinished));
+ if (holder.countdownContainer.getVisibility() != View.VISIBLE) {
+ holder.presentsContainer.setVisibility(View.INVISIBLE);
+ holder.countdownContainer.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ // Check if next stream card should be displayed
+ if (mNextStreamEntry != null && mStream != null &&
+ SantaPreferences.getCurrentTime() >= mNextStreamEntry.timestamp) {
+ announceNewCard(addStreamEntry(mNextStreamEntry));
+ mNextStreamEntry = mStream.getNext();
+ }
+ mSantaCamTimeout.check();
+ }
+
+ 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 TrackerCard addStreamEntry(StreamEntry entry) {
+ SantaLog.d(TAG, "Add Stream entry: " + entry.timestamp);
+ return mAdapter.addStreamEntry(entry);
+ }
+
+ 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, mRecyclerView, mAccessibilityManager);
+ }
+ }
+
+ /**
+ * Called when the state of santa cam mode changes (It is enabled or disabled).
+ */
+ public void onSantacamStateChange(boolean santacamEnabled) {
+
+ // Hide/show the SantaCam ActionBar item if it has been initialised
+ // (Otherwise the visibility is set when it is initialised.)
+ if (mSantaCamButton != null && !isFinishing()) {
+ if (santacamEnabled) {
+ mSantaCamButton.hide();
+ } else {
+ mSantaCamButton.show();
+ }
+ }
+
+ if (santacamEnabled) {
+ mSantaCamTimeout.cancel();
+ }
+ }
+
+ @Override
+ public void onShowDestination(Destination destination) {
+ // TODO: Jump tot the destination
+ mAdapter.addDestination(true, destination, mSupportStreetView);
+ mAdapter.notifyDataSetChanged();
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+
+ @Override
+ public void onClearDestination() {
+ mRecyclerView.smoothScrollToPosition(0);
+ }
+
+ /**
+ * Called when the map is clicked
+ */
+ @Override
+ public void mapClickAction() {
+ // Nothing to do
+ }
+
+ @Override
+ public Destination getDestination(int id) {
+ return null;
+ }
+
+ public void notifyCamInteraction() {
+ if (mSantaCamTimeout != null) {
+ mSantaCamTimeout.reset();
+ }
+ }
+
+ 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(mRecyclerView.getContext(), youtubeId);
+ startActivity(intent);
+ }
+ };
+
+ private static class IncomingHandler extends Handler {
+
+ private final WeakReference mActivityRef;
+
+ public IncomingHandler(SantaMapActivity activity) {
+ mActivityRef = new WeakReference<>(activity);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ SantaLog.d(TAG, "message=" + msg.what);
+ SantaMapActivity 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_UPDATED_CASTDISABLED:
+ activity.mFlagDisableCast = (msg.arg1 == SantaServiceMessages.DISABLED);
+ activity.onCastFlagUpdate();
+ 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, SantaMapActivity activity) {
+ return newOffset >
+ activity.mOffset + SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE ||
+ newOffset <
+ activity.mOffset - SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE;
+ }
+
+ private void onMessageUpdatedTimes(SantaMapActivity 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 void onCastFlagUpdate() {
+ SantaApplication.toogleCast(this, mFlagDisableCast);
+ }
+
+ 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;
+ }
+ };
+
+ private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.santacam:
+ mMapFragment.enableSantaCam(true);
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_tracker),
+ getString(R.string.analytics_tracker_action_cam),
+ getString(R.string.analytics_tracker_cam_fab));
+
+ // [ANALYTICS EVENT]: SantaCamEnabled ActionBar
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
+ R.string.analytics_tracker_action_cam,
+ R.string.analytics_tracker_cam_fab);
+ break;
+ case R.id.top:
+ mRecyclerView.smoothScrollToPosition(0);
+ break;
+ }
+ }
+ };
+
+ private BottomSheetBehavior.BottomSheetListener mBottomSheetListener
+ = new BottomSheetBehavior.BottomSheetListener() {
+
+ private static final float FAB_THRESHOLD = 0.8f;
+
+ @Override
+ public void onStateChanged(@BottomSheetBehavior.State int newState) {
+ adjustMapPaddings(newState);
+ }
+
+ @Override
+ public void onSlide(float slideOffset) {
+ // Hide/show the FAB
+ if (mSantaCamButton != null) {
+ if (mSantaCamButton.getVisibility() == View.VISIBLE) {
+ if (slideOffset > FAB_THRESHOLD) {
+ mSantaCamButton.hide();
+ }
+ } else if (!mMapFragment.isInSantaCam()) {
+ if (slideOffset <= FAB_THRESHOLD) {
+ mSantaCamButton.show();
+ }
+ }
+ }
+ }
+ };
+
+ private void adjustMapPaddings(@BottomSheetBehavior.State int newState) {
+ if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
+ mMapFragment.setCamPadding(0, 0, 0, mBottomSheetBehavior.getPeekHeight() -
+ mBottomSheetBehavior.getHiddenPeekHeight());
+ } else if (newState == BottomSheetBehavior.STATE_HIDDEN) {
+ mMapFragment.setCamPadding(0, 0, 0, 0);
+ }
+ }
+
+ private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
+ boolean mShowTopButton = false;
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ boolean showButton = false;
+ if (mLayoutManager.findFirstVisibleItemPosition() > CardAdapter.DASHBOARD_POSITION) {
+ showButton = true;
+ }
+
+ // Only animate if the button state changes
+ Animation ani = null;
+ if (showButton && !mShowTopButton) {
+ ani = new AlphaAnimation(0, 1);
+ mButtonTop.setVisibility(View.VISIBLE);
+ } else if (!showButton && mShowTopButton) {
+ ani = new AlphaAnimation(1, 0);
+ ani.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mButtonTop.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ }
+
+ if (ani != null) {
+ ani.setDuration(300);
+ ani.setInterpolator(new AccelerateInterpolator());
+ mButtonTop.startAnimation(ani);
+ }
+
+ mShowTopButton = showButton;
+ }
+ };
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapFragment.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapFragment.java
new file mode 100644
index 000000000..ade6bd971
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMapFragment.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2015 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.annotation.SuppressLint;
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.AudioPlayer;
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.Destination;
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.map.DestinationInfoWindowAdapter.DestinationInfoWindowInterface;
+import com.google.android.apps.santatracker.map.SantaMarker.SantaMarkerInterface;
+import com.google.android.apps.santatracker.map.cameraAnimations.AtLocation;
+import com.google.android.apps.santatracker.map.cameraAnimations.SantaCamAnimator;
+import com.google.android.apps.santatracker.util.AnalyticsManager;
+import com.google.android.apps.santatracker.util.MeasurementManager;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.GoogleMap.CancelableCallback;
+import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
+import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
+import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
+import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
+import com.google.android.gms.maps.MapFragment;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.UiSettings;
+import com.google.android.gms.maps.model.BitmapDescriptor;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+/**
+ * A specialised {@link MapFragment} that displays Santa's destinations and
+ * holds a {@link SantaMarker}. The attaching activity MUST implement {@link SantaMapInterface}.
+ *
+ * @author jfschmakeit
+ */
+public class SantaMapFragment extends SupportMapFragment implements
+ DestinationInfoWindowInterface, SantaMarkerInterface {
+
+ // The map
+ private GoogleMap mMap = null;
+
+ // Interface
+ private SantaMapInterface mCallback;
+
+ // visited location
+ private BitmapDescriptor MARKERICON_VISITED;
+ private BitmapDescriptor MARKERICON_ACTIVE;
+
+ private static final String TAG = "SantaMap";
+
+ // Identify different types of markers for infowindow
+ public static final String MARKER_PAST = "MARKER_PAST";
+ public static final String MARKER_NEXT = "MARKER_NEXT";
+ public static final String MARKER_ACTIVE = "MARKER_ACTIVE";
+
+ // Next location marker
+ private Marker mNextMarker = null;
+
+ // info window for marker pop-up bubbles
+ private DestinationInfoWindowAdapter mInfoWindowAdapter;
+
+ // Marker used for active marker
+ private Marker mActiveMarker = null;
+ private Marker mCurrentInfoMarker = null;
+ private Marker mPendingInfoMarker = null;
+
+ protected static final LatLng BOGUS_LOCATION = new LatLng(0f, 0f);
+
+ // Santa
+ private SantaMarker mSantaMarker = null;
+
+ // duration of camera animation to santa when SC is enabled
+ public static final int SANTACAM_MOVETOSANTA_DURATION = 2000;
+
+ // duration of camera animation to user destination
+ public static final int SANTACAM_MOVETOSDEST_DURATION = 2000;
+
+ // zoom level of MOVETODEST destination animation
+ public static final float SANTACAM_MOVETOSDEST_ZOOM = 12.f;
+
+
+ // is SantaCam enabled?
+ private boolean mSantaCam = false;
+
+ // Manages audio playback
+ private AudioPlayer mAudioPlayer;
+
+ private SantaCamAnimator mSantaCamAnimator;
+
+ private Handler mHandler = new Handler();
+
+ private FirebaseAnalytics mMeasurement;
+
+ // Padding
+ private int mPaddingCamLeft, mPaddingCamTop, mPaddingCamRight, mPaddingCamBottom;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.mMap = null;
+ mMeasurement = FirebaseAnalytics.getInstance(this.getContext());
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onResume() {
+ super.onResume();
+ getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(GoogleMap map) {
+ setupMap(map);
+ mCallback.onMapInitialised();
+ }
+ });
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // Stop audio and release audio player
+ stopAudio();
+ }
+
+ public void resumeAudio() {
+ mAudioPlayer.resumeAll();
+ mAudioPlayer.unMuteAll();
+ }
+
+ public void pauseAudio() {
+ mAudioPlayer.pauseAll();
+ mAudioPlayer.muteAll();
+ }
+
+ public void stopAudio() {
+ mAudioPlayer.stopAll();
+ }
+
+ /**
+ * Add the sanata marker to the map
+ */
+ private void addSanta() {
+ // create Santa marker
+ mSantaMarker = new SantaMarker(this);
+ }
+
+ /**
+ * Animate the santa marker to destination to arrive at its arrival time.
+ */
+ public void setSantaTravelling(
+ Destination origin, Destination destination, boolean moveCameraToSanta) {
+
+ mAudioPlayer.playTrack(R.raw.ho_ho_ho, false);
+ mAudioPlayer.playTrack(R.raw.sleighbells, true);
+
+ // display next destination marker
+ mNextMarker.setSnippet(Integer.toString(destination.id));
+ mNextMarker.setPosition(destination.position);
+ mNextMarker.setVisible(true);
+
+ if (moveCameraToSanta) {
+ mSantaCamAnimator.reset();
+ }
+ mSantaMarker.animateTo(origin.position, destination.position,
+ origin.departure, destination.arrival);
+ }
+
+ /**
+ * Sets santa as visiting the given location.
+ */
+ public void setSantaVisiting(Destination destination, boolean playSound) {
+
+ // move santa to this location
+ mSantaMarker.setVisiting(destination.position);
+
+ // stop bells and play 'hohoho'
+ mAudioPlayer.stop(R.raw.sleighbells);
+ if (playSound) {
+ mAudioPlayer.playTrack(R.raw.ho_ho_ho, false);
+ }
+
+ // hide the next marker from this position, move it off-screen to
+ // prevent touch events
+ mNextMarker.setVisible(false);
+ mNextMarker.setPosition(BOGUS_LOCATION);
+
+ // if the infowindow for this position is open, dismiss it
+ if (mActiveMarker != null && mActiveMarker.isVisible()
+ && mActiveMarker.getSnippet().equals(
+ Integer.toString(destination.id))) {
+ hideInfoWindow();
+ }
+
+ }
+
+ public boolean isInSantaCam() {
+ return mSantaCam;
+ }
+
+ public void jumpToDestination(final LatLng position) {
+
+ disableSantaCam();
+
+ if (mSantaMarker != null) {
+ // present drawing will be resumsed when SantaCam is enabled again.
+ mSantaMarker.pausePresentsDrawing();
+ }
+
+ if (mMap != null) {
+ mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position,
+ SANTACAM_MOVETOSDEST_ZOOM),
+ SANTACAM_MOVETOSDEST_DURATION, null);
+ }
+ }
+
+ /**
+ * Enables the SantaCam and (if set) animates the camera to santa.
+ */
+ public void enableSantaCam(boolean animateToSanta) {
+
+ if (mMap != null) {
+ mMap.setPadding(mPaddingCamLeft, mPaddingCamTop, mPaddingCamRight, mPaddingCamBottom);
+ }
+
+ mSantaCam = true;
+ mCallback.onSantacamStateChange(true);
+
+ // hide current infowindow
+ hideInfoWindow();
+
+ // Toast.makeText(this.getActivity(), "SantaCam Enabled",
+ // Toast.LENGTH_SHORT).show();
+
+ mSantaCamAnimator.reset();
+
+ if (animateToSanta) {
+ // santa is already enroute, start animation to Santa and pause animator to speed up
+ // camera animation
+ if (!mSantaMarker.isVisiting()) {
+ mSantaCamAnimator.pause();
+ mMap.animateCamera(CameraUpdateFactory.newLatLng(mSantaMarker.getFuturePosition(
+ SantaPreferences.getCurrentTime() + SANTACAM_MOVETOSANTA_DURATION)),
+ SANTACAM_MOVETOSANTA_DURATION, mMovingCatchupCallback);
+ } else {
+ // Santa is at a location
+ onSantaReachedDestination(mSantaMarker.getPosition());
+ }
+ } else {
+ mSantaMarker.resumePresentsDrawing();
+ }
+
+ }
+
+ public void disableSantaCam() {
+ if (mSantaCam) {
+ mSantaCam = false;
+ mCallback.onSantacamStateChange(false);
+
+ mSantaCamAnimator.cancel();
+ }
+ }
+
+ /**
+ * Called when Santa has reached the given destination.
+ */
+ public void onSantaReachedDestination(final LatLng destination) {
+ // hide the next marker from this position
+ mNextMarker.setVisible(false);
+
+ // Santa has reached destination - update camera
+ // center on Santa's current position at lower zoom level
+ if (mSantaCam) {
+ // Post camera update through Handler to allow for subsequent camera animation in
+ // CancellableCallback
+ mHandler.post(mReachedDestinationRunnable);
+ }
+ mSantaCamAnimator.reset();
+
+ }
+
+ /**
+ * Santa is currently moving, called with a progress update. If in SantaCam,
+ * the camera is repositioned to capture santa.
+ */
+ public void onSantaIsMovingProgress(LatLng position, long remainingTime,
+ long elapsedTime) {
+
+ if (mSantaCam && mMap != null && mSantaMarker != null && position != null
+ && mSantaMarker.getPosition() != null) {
+ // use animator to update camera if in santa cam mode
+ mSantaCamAnimator.animate(position, remainingTime, elapsedTime);
+ }
+ }
+
+ /*
+ * On map click - disable info window if it is displayed, otherwise disable
+ * santa cam or do nothing
+ */
+ private OnMapClickListener mMapClickListener = new OnMapClickListener() {
+
+ public void onMapClick(LatLng arg0) {
+ if (mCallback == null) {
+ // This can happen on orientation change
+ return;
+ }
+ if (mCurrentInfoMarker != null) {
+ // info window is displayed, hide it
+ restoreClickedMarker();
+ mCallback.onClearDestination();
+ } else {
+ mCallback.mapClickAction();
+ }
+ }
+ };
+
+ private OnCameraChangeListener mCameraChangeListener = new OnCameraChangeListener() {
+
+ private float mPreviousBearing = Float.MIN_VALUE;
+
+ public void onCameraChange(CameraPosition camera) {
+ // Notify santa marker if new bearing
+ if (mPreviousBearing != camera.bearing) {
+ mSantaMarker.setCameraOrientation(camera.bearing);
+ mPreviousBearing = camera.bearing;
+ }
+ }
+ };
+
+ /**
+ * Marker click listener. Handles clicks on markers. When a destination
+ * marker is clicked, the active marker is set to this position and the
+ * corresponding infowindow is displayed. If santa cam is enabled, it is
+ * disabled.
+ */
+ private OnMarkerClickListener mMarkerClickListener = new OnMarkerClickListener() {
+
+ public boolean onMarkerClick(Marker marker) {
+
+ // unsupported marker
+ if (marker.getTitle() == null) {
+ return false;
+ }
+ // Santa Marker
+ else if (marker.getTitle().equals(SantaMarker.TITLE)) {
+
+ mAudioPlayer.playTrack(R.raw.ho_ho_ho, false);
+
+ hideInfoWindow();
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_tracker),
+ getString(R.string.analytics_tracker_action_clicksanta), null);
+
+ // [ANALYTICS EVENT]: SantaClicked
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
+ R.string.analytics_tracker_action_clicksanta);
+
+ // spin camera around if santa is not moving in SC
+ // or at any other time if not in SC
+ if ((mSantaCam && mSantaMarker.isVisiting()) || !mSantaCam) {
+ // spin camera around to opposite side
+ CameraPosition oldCamera = mMap.getCameraPosition();
+ float bearing = oldCamera.bearing;
+ // calculate bearing, +1 so that the camera always moves in
+ // the
+ // same direction
+ bearing = (bearing + 181f) % 360f;
+
+ CameraPosition camera = CameraPosition.builder(oldCamera)
+ .bearing(bearing).build();
+ mMap.animateCamera(
+ CameraUpdateFactory.newCameraPosition(camera),
+ SANTACAM_MOVETOSANTA_DURATION,
+ new CancelableCallback() {
+
+ public void onFinish() {
+
+ // animate another 181 degrees around
+ CameraPosition oldCamera = mMap
+ .getCameraPosition();
+ float bearing = oldCamera.bearing;
+ bearing = (bearing + 181f) % 360f;
+ CameraPosition camera = CameraPosition
+ .builder(mMap.getCameraPosition())
+ .bearing(bearing).build();
+ mMap.animateCamera(CameraUpdateFactory
+ .newCameraPosition(camera),
+ SANTACAM_MOVETOSANTA_DURATION, null);
+ }
+
+ public void onCancel() {
+
+ }
+ });
+ }
+ return true;
+
+ // Present Marker
+ } else if (marker.getTitle().equals(PresentMarker.MARKER_TITLE)) {
+
+ return true;
+
+ // Pin marker (location)
+ } else if (marker.getTitle().equals(MARKER_NEXT)
+ || marker.getTitle().equals(MARKER_PAST)) {
+
+ showInfoWindow(marker);
+
+ return true;
+
+ // Active marker
+ } else if (marker.getTitle().equals(MARKER_ACTIVE)) {
+
+ hideInfoWindow();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ };
+
+
+ /**
+ * Converts from degrees to radians.
+ *
+ * @return Result in radians.
+ */
+ private static double sphericalDdegreesToRadians(double deg) {
+ return deg * (Math.PI / 180);
+ }
+
+ /**
+ * Converts from radians to degrees.
+ *
+ * @param rad Input in radians.
+ * @return Result in degrees.
+ */
+ private static double sphericalRadiansToDegrees(double rad) {
+ return rad / (Math.PI / 180);
+ }
+
+ /**
+ * Wraps the given value into the inclusive-exclusive interval between min
+ * and max.
+ *
+ * @param {number} value The value to wrap.
+ * @param {number} min The minimum.
+ * @param {number} max The maximum.
+ * @return {number} The result.
+ */
+ private static double sphericalWrap(double value, double min, double max) {
+ return sphericalMod(value - min, max - min) + min;
+ }
+
+ /**
+ * Returns the non-negative remainder of x / m.
+ *
+ * @param x The operand.
+ * @param m The modulus.
+ */
+ private static double sphericalMod(double x, double m) {
+ return ((x % m) + m) % m;
+ }
+
+ /**
+ * Activity is attaching to this fragment, ensure it is implementing
+ * {@link SantaMapInterface}.
+ */
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ mAudioPlayer = new AudioPlayer(activity.getApplicationContext());
+
+ // ensure that attaching activity implements the required interface
+ try {
+ mCallback = (SantaMapInterface) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString()
+ + " must implement SantaMapInterface");
+ }
+
+ }
+
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallback = null;
+ mAudioPlayer.stopAll();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if (this.mMap != null) {
+ this.mMap.clear();
+ }
+
+ // reset map to trigger new setup
+ this.mMap = null;
+
+ // stop santa's animation thread
+ if (mSantaMarker != null) {
+ mSantaMarker.stopAnimations();
+ }
+ if (this.mSantaCamAnimator != null) {
+ this.mSantaCamAnimator.cancel();
+ }
+
+ pauseAudio();
+ }
+
+ /**
+ * Sets up the map and member variables. This method should be called once
+ * the map has been initialised.
+ */
+ private void setupMap(GoogleMap map) {
+ SantaLog.d(TAG, "Setup map.");
+ mMap = map;
+
+ mInfoWindowAdapter = new DestinationInfoWindowAdapter(
+ getLayoutInflater(null), this, getActivity()
+ .getApplicationContext());
+
+ // clear map in case it was restored
+ mMap.clear();
+
+ // setup map UI - disable zoom controls
+ UiSettings ui = this.mMap.getUiSettings();
+ ui.setZoomControlsEnabled(false);
+ ui.setCompassEnabled(false);
+
+ this.mMap.setInfoWindowAdapter(mInfoWindowAdapter);
+ this.mMap.setOnInfoWindowClickListener(mInfoWindowClickListener);
+ this.mMap.setOnMapClickListener(mMapClickListener);
+ this.mMap.setOnCameraChangeListener(mCameraChangeListener);
+ this.mMap.setOnMarkerClickListener(mMarkerClickListener);
+
+ // setup marker icons
+ MARKERICON_VISITED = BitmapDescriptorFactory
+ .fromResource(R.drawable.marker_pin);
+ MARKERICON_ACTIVE = BitmapDescriptorFactory
+ .fromResource(R.drawable.marker_pin_blue);
+
+ // add active marker
+ mActiveMarker = mMap.addMarker(new MarkerOptions()
+ .position(BOGUS_LOCATION).icon(MARKERICON_ACTIVE)
+ .title(MARKER_ACTIVE).visible(false).snippet("0")
+ .anchor(0.5f, 1f));
+ mActiveMarker.setVisible(false); // required, visible in MarkerOptions
+ // does not work
+
+ // add next marker
+ mNextMarker = mMap.addMarker(new MarkerOptions()
+ .position(BOGUS_LOCATION)
+ .icon(BitmapDescriptorFactory
+ .fromResource(R.drawable.marker_pin_light))
+ .visible(false).snippet("0").title(MARKER_NEXT)
+ .anchor(0.5f, 1f));
+ mNextMarker.setVisible(false);
+
+ addSanta();
+
+ mSantaCamAnimator = new SantaCamAnimator(mMap, mSantaMarker);
+ }
+
+ public boolean isInitialised() {
+ return this.mMap != null;
+ }
+
+ public GoogleMap getMap() {
+ return mMap;
+ }
+
+ /**
+ * Add a marker for a previous location.
+ */
+ public void addLocation(Destination destination) {
+ // Log.d(TAG, "Adding destination: "+destination
+ // +", map = "+mMap+", init?"+isInitialised());
+ mMap.addMarker(new MarkerOptions().position(destination.position)
+ .icon(MARKERICON_VISITED).anchor(0.5f, 1f).title(MARKER_PAST)
+ .snippet(Integer.toString(destination.id)));
+ }
+
+ public LatLng getSantaPosition() {
+ return mSantaMarker.getPosition();
+ }
+
+ /**
+ * If the active marker is set, hide its infowindow and restore the original
+ * marker.
+ */
+ private void restoreClickedMarker() {
+ if (this.mCurrentInfoMarker != null) {
+ mActiveMarker.hideInfoWindow();
+ mActiveMarker.setVisible(false);
+ mCurrentInfoMarker.setPosition(mActiveMarker.getPosition());
+ mActiveMarker.setPosition(BOGUS_LOCATION);
+ mCurrentInfoMarker.setVisible(true);
+ mCurrentInfoMarker = null;
+ }
+ }
+
+ /**
+ * Hides the current info window if it is displayed
+ */
+ public void hideInfoWindow() {
+ if (this.mCurrentInfoMarker != null) {
+ restoreClickedMarker();
+ mCallback.onClearDestination();
+ }
+ }
+
+
+ /**
+ * Callback that retrieves a {@link Destination} object for the given
+ * identifier.
+ */
+ public Destination getDestinationInfo(int id) {
+ return mCallback.getDestination(id);
+ }
+
+ /**
+ * Display the info window for a marker. The database is queried using a DestinationTask to
+ * retrieve a Destination object.
+ */
+ private void showInfoWindow(Marker marker) {
+ // disable santa cam mode
+ if (mSantaCam) {
+ disableSantaCam();
+ }
+
+ // store tapped marker as pending
+ mPendingInfoMarker = marker;
+
+ // hide window if it is currently displayed.
+ hideInfoWindow();
+ new DestinationTask().execute(Integer.parseInt(marker
+ .getSnippet()));
+
+ // App Measurement
+ MeasurementManager.recordCustomEvent(mMeasurement,
+ getString(R.string.analytics_event_category_tracker),
+ getString(R.string.analytics_tracker_action_location),
+ marker.getSnippet());
+
+ // [ANALYTICS EVENT]: LocationSelected
+ AnalyticsManager.sendEvent(R.string.analytics_event_category_tracker,
+ R.string.analytics_tracker_action_location,
+ marker.getSnippet());
+ }
+
+ private void showInfoWindow(Destination destination) {
+ // ensure that destination data belongs to the pending info marker, ignore otherwise
+ if (mPendingInfoMarker != null && destination != null &&
+ mPendingInfoMarker.getSnippet() != null &&
+ destination.id == Integer.parseInt(mPendingInfoMarker.getSnippet())) {
+ // store selected marker
+ mCurrentInfoMarker = mPendingInfoMarker;
+
+ mPendingInfoMarker.setVisible(false);
+
+ updateActiveDestination(destination, mCurrentInfoMarker);
+ mInfoWindowAdapter.setData(destination);
+ mActiveMarker.showInfoWindow();
+ mPendingInfoMarker = null;
+
+ mCallback.onShowDestination(destination);
+ }
+
+ }
+
+ /**
+ * Adds the Marker to the map.
+ */
+ public Marker addMarker(MarkerOptions m) {
+ return this.mMap.addMarker(m);
+ }
+
+ /**
+ * Sets the active marker to the given destination and makes it visible.
+ */
+ public void updateActiveDestination(Destination destination,
+ Marker clickedMarker) {
+ mActiveMarker.setPosition(destination.position);
+ clickedMarker.setPosition(BOGUS_LOCATION);
+ mActiveMarker.setVisible(true);
+ mActiveMarker.setSnippet("" + destination.id);
+ }
+
+ /**
+ * Info Window Click listener. When an infowindow is clicked, the displayed
+ * infowindow is dismissed.
+ */
+ private OnInfoWindowClickListener mInfoWindowClickListener = new OnInfoWindowClickListener() {
+
+ public void onInfoWindowClick(Marker arg0) {
+ // dismiss the infowindow and restore original marker
+ hideInfoWindow();
+ }
+ };
+
+ public void setCamPadding(int left, int top, int right, int bottom) {
+ mPaddingCamLeft = left;
+ mPaddingCamTop = top;
+ mPaddingCamRight = right;
+ mPaddingCamBottom = bottom;
+ getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ googleMap.setPadding(mPaddingCamLeft, mPaddingCamTop,
+ mPaddingCamRight, mPaddingCamBottom);
+ }
+ });
+ if (mSantaCam && mSantaMarker != null) {
+ mSantaCamAnimator.triggerPaddingAnimation();
+ }
+ }
+
+ private CancelableCallback mMovingCatchupCallback = new CancelableCallback() {
+ @Override
+ public void onFinish() {
+ mSantaCamAnimator.resume();
+ mSantaMarker.resumePresentsDrawing();
+ }
+
+ @Override
+ public void onCancel() {
+ mSantaCamAnimator.resume();
+ }
+ };
+
+ private CancelableCallback mReachedAnimationCallback = new CancelableCallback() {
+
+ @Override
+ public void onFinish() {
+ mHandler.post(mMoveToSantaRunnable);
+ }
+
+ @Override
+ public void onCancel() {
+ // ignore
+ }
+ };
+
+ /**
+ * Move camera: Reached destination in santa cam mode
+ */
+ Runnable mReachedDestinationRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mMap != null) {
+ mMap.animateCamera(AtLocation
+ .GetCameraUpdate(mSantaMarker.getPosition(),
+ mMap.getCameraPosition().bearing),
+ SANTACAM_MOVETOSANTA_DURATION, mReachedAnimationCallback);
+ }
+ }
+ };
+
+ /**
+ * Move camera to center on Santa
+ */
+ public Runnable mMoveToSantaRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mMap != null && mSantaMarker != null && mSantaMarker.getDestination() != null) {
+ mMap.animateCamera(CameraUpdateFactory.newLatLng(mSantaMarker.getPosition())
+ , SANTACAM_MOVETOSANTA_DURATION, null);
+ }
+ }
+ };
+
+ public void moveMapBy(float x, float y) {
+ Log.d(TAG, "move camera by:" + x + ", " + y);
+ mMap.animateCamera(CameraUpdateFactory.scrollBy(x, y));
+ }
+
+ /**
+ * AsyncTask that queries the database for a destination.
+ */
+ private class DestinationTask extends AsyncTask {
+
+ @Override
+ protected Destination doInBackground(Integer... params) {
+ DestinationDbHelper dbHelper = DestinationDbHelper
+ .getInstance(getActivity().getApplicationContext());
+ return dbHelper.getDestination(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Destination destination) {
+ showInfoWindow(destination);
+ }
+ }
+
+ /**
+ * Interface for callbacks from this Fragment.
+ *
+ * @author jfschmakeit
+ */
+ public interface SantaMapInterface {
+
+ /**
+ * Called when the map has been initialised and is ready to be used.
+ */
+ void onMapInitialised();
+
+ void mapClickAction();
+
+ /**
+ * Returns the destination with the given id
+ */
+ Destination getDestination(int id);
+
+ /**
+ * Called when the santacam is enabled or disabled.
+ */
+ void onSantacamStateChange(boolean santacamEnabled);
+
+ void onShowDestination(Destination destination);
+
+ void onClearDestination();
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMarker.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMarker.java
new file mode 100644
index 000000000..bfd797bd9
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/SantaMarker.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2015 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 com.google.android.apps.santatracker.R;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.maps.model.Polyline;
+import com.google.android.gms.maps.model.PolylineOptions;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.maps.android.SphericalUtil;
+
+import android.graphics.Color;
+import android.os.Handler;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * Manages the Santa Marker on a {@link SantaMapFragment}.
+ *
+ * @author jfschmakeit
+ */
+public class SantaMarker {
+
+ // private static final String TAG = "SantaMarker";
+
+ /**
+ * Snippet used by all markers that make up a santa marker (including all
+ * animation frame markers).
+ */
+ public static final String TITLE = "santamarker";
+
+ // The santa marker
+ private Marker[] mMovementMarkers;
+
+ // The map to which this marker is attached
+ private SantaMapFragment mMap;
+
+ // The movement thread
+ private SantaMarkerMovementThread mMovementThread = null;
+
+ // The animation thread (marker icon)
+ private SantaMarkerAnimationThread mAnimationThread = null;
+ private Marker[] mAnimationMarkers;
+ private int[] mAnimationIcons = new int[]{
+ R.drawable.marker_santa_presents1,
+ R.drawable.marker_santa_presents2,
+ R.drawable.marker_santa_presents3,
+ R.drawable.marker_santa_presents4,
+ R.drawable.marker_santa_presents5,
+ R.drawable.marker_santa_presents6,
+ R.drawable.marker_santa_presents7,
+ R.drawable.marker_santa_presents8};
+
+ // line colour
+ private static final int mLineColour = Color.parseColor("#AAd63931");
+
+ // Santa's path
+ private Polyline mPath = null;
+
+ // 2D array: for each present type, 4 types of presents, 0=100
+ private static final int[][] PRESENTS = {
+ {R.drawable.blue_100, R.drawable.blue_75, R.drawable.blue_50,
+ R.drawable.blue_25},
+ {R.drawable.purple_100, R.drawable.purple_75,
+ R.drawable.purple_50, R.drawable.purple_25},
+ {R.drawable.yellow_100, R.drawable.yellow_75,
+ R.drawable.yellow_50, R.drawable.yellow_25},
+ {R.drawable.red_100, R.drawable.red_75,
+ R.drawable.red_50, R.drawable.red_25},
+ {R.drawable.green_100, R.drawable.green_75,
+ R.drawable.green_50, R.drawable.green_25}};
+
+ /**
+ * Markers for santa movement
+ */
+ private static final int[] MOVEMENT_MARKERS = new int[]{
+ R.drawable.santa_n, R.drawable.santa_ne, R.drawable.santa_e,
+ R.drawable.santa_se, R.drawable.santa_s, R.drawable.santa_sw,
+ R.drawable.santa_w, R.drawable.santa_nw, R.drawable.santa_n,};
+
+ // orientation of camera
+ private double mCameraOrientation;
+ // Santa's heading when moving
+ private double mHeading = -1;
+ // current movement marker
+ private int mMovingMarker = 0;
+
+ private PresentMarker[] mPresentMarkers;
+
+ private Handler mUIHandler;
+ // State of Santa Marke - visiting or travelling
+ private boolean mVisiting = false;
+
+ // Flag to indicate whether draw presents or not.
+ private boolean mPresentsDrawingPaused = false;
+
+ /**
+ * Santa's position.
+ */
+ private LatLng mPosition = new LatLng(0,0);
+
+ public SantaMarker(SantaMapFragment map) {
+ super();
+ this.mMap = map;
+
+ LatLng tempLocation = new LatLng(0, 0);
+
+ // setup array of Santa animation markers and make them invisible
+ mAnimationMarkers = new Marker[mAnimationIcons.length];
+ for (int i = 0; i < mAnimationIcons.length; i++) {
+ Marker m = addSantaMarker(mAnimationIcons[i], 0.5f, 1f,
+ tempLocation);
+ m.setVisible(false);
+ mAnimationMarkers[i] = m;
+ }
+
+ mUIHandler = new Handler();
+
+ // Present marker
+ View v = map.getView();
+ mPresentMarkers = new PresentMarker[PRESENTS.length];
+ for (int i = 0; i < mPresentMarkers.length; i++) {
+ mPresentMarkers[i] = new PresentMarker(map.getMap(), this,
+ new Handler(), PRESENTS[i], v.getWidth(), v.getHeight());
+ }
+
+ // Movement markers
+ mMovementMarkers = new Marker[MOVEMENT_MARKERS.length];
+ for (int i = 0; i < MOVEMENT_MARKERS.length; i++) {
+ mMovementMarkers[i] = addSantaMarker(MOVEMENT_MARKERS[i], 0.5f,
+ 0.5f, tempLocation);
+ mMovementMarkers[i].setVisible(false);
+ }
+
+ mMovingMarker = 0;
+
+ }
+
+ /**
+ * Move all Markers used for the present animation to the given position
+ */
+ private void moveAnimationMarkers(LatLng position) {
+ for (Marker m : mAnimationMarkers) {
+ m.setPosition(position);
+ }
+ }
+
+ /**
+ * Adds a new marker at the given position. u, describes the anchor
+ * position.
+ */
+ private Marker addSantaMarker(int iconDrawable, float u, float v,
+ LatLng position) {
+ Marker m = this.mMap.addMarker(new MarkerOptions().position(position)
+ .anchor(u, v)
+ /* anchor in center */
+ .title(TITLE)
+ .icon(BitmapDescriptorFactory.fromResource(iconDrawable)));
+
+ return m;
+ }
+
+ /**
+ * Sets the camera orientation and update the marker if moving.
+ */
+ public void setCameraOrientation(float bearing) {
+ this.mCameraOrientation = (bearing + 360.0f) % 360.0f;
+
+ if (mMovementThread != null && mMovementThread.isMoving()) {
+ setMovingIcon();
+ }
+ }
+
+ /**
+ * Update the movement marker.
+ */
+ private void setMovingIcon() {
+
+ double angle = ((mHeading - mCameraOrientation + 360.0)) % 360.0;
+ int index = ((int) (Math.round((Math.abs(angle) / 360f)
+ * (mMovementMarkers.length - 1)))) % mMovementMarkers.length;
+
+ setMovingMarker(index);
+
+ // Log.d("SantaMarker", "Moving icon = camera:" + mCameraOrientation
+ // + ", heading:" + mHeading + ",angle=" + angle + ", index="+index);
+ }
+
+ /**
+ * Hides the previous marker, moves the new marker and makes it visible.
+ */
+ private void setMovingMarker(int i) {
+ if (mMovingMarker != i) {
+ LatLng pos = mMovementMarkers[mMovingMarker].getPosition();
+ mMovementMarkers[i].setPosition(pos);
+ mMovementMarkers[i].setVisible(true);
+ mMovementMarkers[mMovingMarker].setVisible(false);
+ mMovingMarker = i;
+ }
+ }
+
+ /**
+ * Sets the position of the current movement marker.
+ */
+ private void setMovingPosition(LatLng pos) {
+ setCachedPosition(pos);
+ mMovementMarkers[mMovingMarker].setPosition(pos);
+ }
+
+ /**
+ * Hides the current movement marker.
+ */
+ private void hideMovingMarker() {
+ mMovementMarkers[mMovingMarker].setVisible(false);
+ }
+
+ /**
+ * Santa is visiting this location, display animation.
+ */
+ public void setVisiting(LatLng pos) {
+ mVisiting = true;
+
+ setCachedPosition(pos);
+
+ // stopAnimations();
+ removePath();
+
+ this.mAnimationThread = new SantaMarkerAnimationThread(
+ mAnimationMarkers, mUIHandler);
+
+ this.mAnimationThread.startAnimation(pos);
+
+ hideMovingMarker();
+
+ // reset heading
+ this.mHeading = -1;
+
+ }
+
+ public boolean isVisiting() {
+ return mVisiting;
+ }
+
+ /**
+ * Returns the current position of this marker.
+ */
+ public synchronized LatLng getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Saves a location as Santa's current location. This makes it available to other classes in
+ * {@link #getPosition()}.
+ * @param position
+ */
+ private synchronized void setCachedPosition(LatLng position) {
+ mPosition = position;
+ }
+
+ /**
+ * Returns the origin position if the marker is moving, null otherwise.
+ */
+ public LatLng getOrigin() {
+ if (mMovementThread != null) {
+ return mMovementThread.getOrigin();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the destination position if the marker is moving, null otherwise.
+ */
+ public LatLng getDestination() {
+ if (mMovementThread != null) {
+ return mMovementThread.getDestination();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Animate this marker to the given position for the timestamps.
+ */
+ public void animateTo(LatLng originLocation, LatLng destinationLocation,
+ long departure, long arrival) {
+
+ mVisiting = false;
+
+ setMovingIcon();
+ // create new animation runnable and post to handler
+ mMovementThread = new SantaMarkerMovementThread(departure, arrival,
+ destinationLocation, originLocation, mUIHandler, true);
+ mMovementThread.startAnimation();
+
+ if (mAnimationThread != null && mAnimationThread.isAlive()) {
+ mAnimationThread.stopAnimation();
+ }
+
+ }
+
+ /**
+ * Remove the path.
+ */
+ private void removePath() {
+ if (this.mPath != null) {
+ this.mPath.remove();
+ this.mPath = null;
+ }
+ }
+
+ /**
+ * Stops all marker animations. Should be called by attached Activity in
+ * lifecycle methods.
+ */
+ public void stopAnimations() {
+ if (mMovementThread != null) {
+ mMovementThread.stopAnimation();
+ }
+
+ if (mAnimationThread != null) {
+ mAnimationThread.stopAnimation();
+ }
+ }
+
+ /**
+ * If this marker is currently moving, calculate its future position at the
+ * given timestamp. If this marker is not moving, return its current
+ * position
+ */
+ public LatLng getFuturePosition(long timestamp) {
+ if (mMovementThread != null && mMovementThread.isMoving()) {
+ return mMovementThread.calculatePosition(timestamp);
+ } else {
+ return getPosition();
+ }
+ }
+
+ public void pausePresentsDrawing() {
+ mPresentsDrawingPaused = true;
+ }
+
+ public void resumePresentsDrawing() {
+ mPresentsDrawingPaused = false;
+ }
+
+ /**
+ * Thread that toggles visibility of the markers, making one marker at a
+ * time visible.
+ *
+ * @author jfschmakeit
+ */
+ public class SantaMarkerAnimationThread extends Thread {
+
+ /*
+ * The refresh rate is identical to the marker movement thread to
+ * animate the presents
+ */
+ public static final int REFRESH_RATE = SantaMarkerMovementThread.REFRESH_RATE;
+ public static final int ANIMATION_DELAY = 6; // should be equivalent to
+ // a postdelay of 150ms
+ private Marker[] mToggleMarkers;
+ private Handler mHandler;
+ private int mCurrent = 0;
+ private int mFrame = 0;
+ private boolean mStopThread = false;
+ private SwapMarkersRunnable mSwapRunnable;
+ private final LatLng TEMP_POSITION = new LatLng(0f, 0f);
+
+ public SantaMarkerAnimationThread(Marker[] mMarkers, Handler mHandler) {
+ super();
+ this.mToggleMarkers = mMarkers;
+ this.mHandler = mHandler;
+ this.mSwapRunnable = new SwapMarkersRunnable();
+ }
+
+ public void run() {
+ while (!this.mStopThread) {
+ if (mFrame == 0) {
+
+ final int currentMarker = mCurrent;
+ final int nextMarker = (++mCurrent % mToggleMarkers.length);
+ mCurrent = nextMarker;
+
+ mSwapRunnable.currentMarker = currentMarker;
+ mSwapRunnable.nextMarker = nextMarker;
+ mHandler.post(mSwapRunnable);
+ }
+ mFrame = (mFrame + 1) % ANIMATION_DELAY;
+
+ for (PresentMarker m : mPresentMarkers) {
+ m.draw();
+ }
+
+ try {
+ Thread.sleep(REFRESH_RATE);
+ } catch (InterruptedException e) {
+ // if interrupted, cancel
+ this.mStopThread = true;
+ }
+ }
+ }
+
+ /**
+ * Hide and move markers, need to restart thread to make visible again.
+ */
+ public void hideAll() {
+ for (Marker m : mToggleMarkers) {
+ m.setVisible(false);
+ m.setPosition(TEMP_POSITION);
+ }
+ }
+
+ public void cancel() {
+ this.mStopThread = true;
+ }
+
+ /**
+ * Start this thread. All animated markers (and the normal santa marker)
+ * are hidden.
+ */
+ public void startAnimation(LatLng position) {
+ this.mStopThread = false;
+ hideAll();
+
+ setCachedPosition(position);
+
+ for (PresentMarker m : mPresentMarkers) {
+ m.reset();
+ }
+
+ moveAnimationMarkers(position);
+
+ this.start();
+ }
+
+ /**
+ * Stop this thread. All animated markers are hidden and the original
+ * santa marker is made visible.
+ */
+ public void stopAnimation() {
+ // stop execution by removing all callbacks
+ this.mStopThread = true;
+ mHandler.removeCallbacksAndMessages(null);
+ hideAll();
+ }
+
+ class SwapMarkersRunnable implements Runnable {
+
+ public int currentMarker, nextMarker;
+
+ public void run() {
+ if (mMap != null && mMap.getMap() != null) {
+ mToggleMarkers[currentMarker].setVisible(false);
+ mToggleMarkers[nextMarker].setVisible(true);
+
+ float zoom = mMap.getMap().getCameraPosition().zoom;
+ PresentMarker.setViewParameters(zoom, mMap.isInSantaCam());
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Animation Thread for a Santa Marker. Animates the marker between two
+ * locations.
+ *
+ * @author jfschmakeit
+ */
+ public class SantaMarkerMovementThread extends Thread {
+
+ /**
+ * Refresh rate of this thread (it is called again every X ms.)
+ */
+ public static final int REFRESH_RATE = 17;
+
+ private boolean mStopThread = false;
+ private long start, arrival;
+ private double duration;
+ private LatLng destinationLocation, startLocation;
+ private Handler handler;
+ private boolean mIsAnimated = false;
+ private ArrayList pathPoints;
+
+ // Threads
+ private MovementRunnable mMovementRunnable;
+
+ public SantaMarkerMovementThread(long departureTime, long arrivalTime,
+ LatLng destinationLocation, LatLng startLocation,
+ Handler handler, boolean drawPath) {
+ this.start = departureTime;
+ this.arrival = arrivalTime;
+ this.destinationLocation = destinationLocation;
+ this.startLocation = startLocation;
+ this.handler = handler;
+ this.duration = arrivalTime - departureTime;
+
+ removePath();
+ if (drawPath) {
+ // set up path
+ PolylineOptions line = new PolylineOptions().add(startLocation)
+ .add(startLocation).color(mLineColour);
+ mPath = mMap.getMap().addPolyline(line);
+ mPath.setGeodesic(true);
+ pathPoints = new ArrayList(2);
+ pathPoints.add(startLocation); // origin
+ pathPoints.add(startLocation); // destination - updated in loop
+ } else {
+ mPath = null; // already removed
+ }
+
+ mMovementRunnable = new MovementRunnable();
+
+ }
+
+ public void stopAnimation() {
+ this.mStopThread = true;
+ handler.removeCallbacksAndMessages(null);
+ }
+
+ public void startAnimation() {
+ this.mStopThread = false;
+ start();
+ }
+
+ private Runnable mSetIconRunnable = new Runnable() {
+ public void run() {
+ setMovingIcon();
+ }
+ };
+ private Runnable mReachedDestinationRunnable = new Runnable() {
+
+ public void run() {
+ removePath();
+ // notify callback
+ mMap.onSantaReachedDestination(destinationLocation);
+ }
+ };
+
+ public void run() {
+ while (!mStopThread) {
+ // need to initialise, marker not set as animated yet
+ if (!mIsAnimated) {
+
+ mIsAnimated = true;
+
+ // calculate heading and update icon
+ mHeading = SphericalUtil.computeHeading(startLocation, destinationLocation);
+ mHeading = (mHeading + 360f) % 360f;
+
+ handler.post(mSetIconRunnable);
+ // Log.d(TAG, "Starting animation thread: from: "
+ // + startLocation + " --to: " + destinationLocation
+ // + ", start=" + start + ", dep=" + arrival
+ // + ", duration=" + duration + ", head=" + mHeading);
+ }
+
+ double t = calculateProgress(SantaPreferences
+ .getCurrentTime());
+
+ //SantaLog.d(TAG,"inSantaAnimateThread: t="+t+", position="+calculatePositionProgress(t)+", stopThread="+mStopThread);
+
+ // Don't go backwards, but it could be negative if this thread is started too early
+ t = Math.max(t, 0.0);
+ // loop until finished or thread was notified to be stopped
+ if (t < 1.0 && !mStopThread) {
+
+ mMovementRunnable.position = calculatePositionProgress(t);
+ // move marker and update path
+ handler.post(mMovementRunnable);
+
+ if (!mPresentsDrawingPaused) {
+ for (PresentMarker p : mPresentMarkers) {
+ p.draw();
+ }
+ }
+
+ try {
+ Thread.sleep(REFRESH_RATE);
+ } catch (InterruptedException e) {
+ this.mStopThread = true;
+ }
+ } else {
+ // reached final destination,stop moving
+ mIsAnimated = false;
+ mStopThread = true;
+ setCachedPosition(destinationLocation);
+
+ handler.post(mReachedDestinationRunnable);
+
+ }
+
+ }
+ }
+
+ /**
+ * Calculate the position for the given future timestamp. If the
+ * destination is reached before this timestmap, its destination is
+ * returned.
+ */
+ public LatLng calculatePosition(long timestamp) {
+ double progress = calculateProgress(timestamp);
+ return calculatePositionProgress(progress);
+ }
+
+ /**
+ * Calculates the progress through the animation for the given timestamp
+ */
+ private double calculateProgress(long currentTimestamp) {
+ return (currentTimestamp - start) / duration; // linear progress
+ }
+
+ /**
+ * Calculate the position for the given progress (start at 0, finished
+ * at 1).
+ */
+ private LatLng calculatePositionProgress(double progress) {
+
+ return SphericalUtil.interpolate(startLocation, destinationLocation, progress);
+ }
+
+ private LatLng getDestination() {
+ return destinationLocation;
+ }
+
+ private LatLng getOrigin() {
+ return startLocation;
+ }
+
+ private boolean isMoving() {
+ return mIsAnimated;
+ }
+
+ class MovementRunnable implements Runnable {
+
+ public LatLng position;
+
+ public void run() {
+ setMovingPosition(position);
+
+ // update path if it is enabled
+ if (mPath != null) {
+ pathPoints.set(1, position);
+ mPath.setPoints(pathPoints);
+ }
+
+ if (mMap.getMap() != null) {
+ float zoom = mMap.getMap().getCameraPosition().zoom;
+ PresentMarker.setViewParameters(zoom, mMap.isInSantaCam());
+ }
+
+ long time = SantaPreferences.getCurrentTime();
+ mMap.onSantaIsMovingProgress(position, arrival - time, time
+ - start);
+
+ }
+ }
+ }
+
+ /**
+ * Interface for callbacks from a {@link SantaMarker}.
+ *
+ * @author jfschmakeit
+ */
+ public interface SantaMarkerInterface {
+
+ public void onSantaReachedDestination(LatLng location);
+
+ public void onSantaIsMovingProgress(LatLng position,
+ long remainingTime, long elapsedTime);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/TvSantaMapActivity.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/TvSantaMapActivity.java
new file mode 100644
index 000000000..abb1f32dd
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/TvSantaMapActivity.java
@@ -0,0 +1,969 @@
+/*
+ * Copyright (C) 2015 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.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 NO_NEXT_DESTINATION;
+
+ // countdown update frequency (in ms)
+ private static final int DESTINATION_COUNTDOWN_UPDATEINTERVAL = 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;
+
+ 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);
+ NO_NEXT_DESTINATION = resources.getString(R.string.no_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);
+ mMapFragment.setCamPadding(widthPadding, heightPadding,
+ widthPadding + cardPadding, 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 mLoaderCallbacks
+ = new LoaderManager.LoaderCallbacks() {
+
+ @Override
+ public Loader 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 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 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);
+
+ // 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_UPDATEINTERVAL) {
+
+ @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 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.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);
+
+ // update fragments with destinations, only update next destination if there is one
+ setNextLocation(DashboardFormats.formatDestination(nextDestination));
+
+ 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_UPDATEINTERVAL) {
+
+ @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 Destination getDestination(int id) {
+ return null;
+ }
+
+ @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 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;
+ }
+ };
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/AtLocation.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/AtLocation.java
new file mode 100644
index 000000000..5f5dd6207
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/AtLocation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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.cameraAnimations;
+
+import com.google.android.gms.maps.CameraUpdate;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+
+public abstract class AtLocation {
+
+ private static final float TILT_MIN = 45f;
+ private static final float TILT_MAX = 37.5f;
+ private static final float ZOOM_MIN = 11f;
+ private static final float ZOOM_MAX = 13f;
+ private static final float BEARING_RANGE = 45f;
+
+ public static CameraUpdate GetCameraUpdate(LatLng position, float camBearing) {
+
+ final float tilt = SantaPreferences.getRandom(TILT_MIN, TILT_MAX);
+ final float zoom = SantaPreferences.getRandom(ZOOM_MIN, ZOOM_MAX);
+
+ // Limit bearing to 45 deg to either side - MapView rotation bug
+ float bearing =
+ SantaPreferences.getRandom(camBearing - BEARING_RANGE, camBearing + BEARING_RANGE);
+ bearing = (bearing + 360f) % 360f;
+
+ final CameraPosition camera = CameraPosition.builder()
+ .target(position).tilt(tilt)
+ .zoom(zoom).bearing(bearing).build();
+
+ return CameraUpdateFactory.newCameraPosition(camera);
+ }
+
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/CurrentPathAnimation.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/CurrentPathAnimation.java
new file mode 100644
index 000000000..87d12032f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/CurrentPathAnimation.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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.cameraAnimations;
+
+import com.google.android.gms.maps.CameraUpdate;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.GoogleMap.CancelableCallback;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.map.SantaMarker;
+
+import android.os.Handler;
+
+/**
+ * Camera animation that shows the path from Santa's current position to the
+ * destination. The camera is animated independently of calls to
+ * {@link #onSantaMoving(LatLng)} until the {@link #MAX_ZOOM} level is reached.
+ *
+ * @author jfschmakeit
+ */
+public class CurrentPathAnimation extends SantaCamAnimation {
+
+ private static final int ANIMATION_DURATION = 2000;
+ private static final int PADDING = 50; // TODO: move to constructor
+
+ public static final float MAX_ZOOM = 10f;
+
+ private CameraUpdate mCameraUpdate;
+
+ public CurrentPathAnimation(Handler handler, GoogleMap mMap,
+ SantaMarker mSanta) {
+ super(handler, mMap, mSanta);
+ }
+
+ public void start() {
+
+ // Start the first animation - zoom to capture the current position and
+ // destination
+ animateShowSantaDestination(mSanta.getPosition());
+
+ }
+
+ // animate to a new bounds with santa and his destination
+ Runnable mThreadAnimate = new Runnable() {
+
+ public void run() {
+ if (mCameraUpdate != null && mMap != null) {
+ mMap.animateCamera(mCameraUpdate, ANIMATION_DURATION,
+ mCancelCallback);
+ }
+ }
+ };
+
+ /**
+ * Animate showing the destination and the position.
+ */
+ private void animateShowSantaDestination(LatLng futurePosition) {
+ final LatLng santaDestination = (mSanta != null) ? mSanta.getDestination() : null;
+
+ // Only construct a camera update if both positions are valid
+ if(futurePosition == null || santaDestination == null){
+ return ;
+ }
+
+ mCameraUpdate = CameraUpdateFactory.newLatLngBounds(
+ new LatLngBounds.Builder().include(futurePosition)
+ .include(santaDestination).build(), PADDING);
+ executeRunnable(mThreadAnimate);
+
+ }
+
+ /**
+ * Animate at current zoom level to center on the position.
+ */
+ private void animateFollowSanta(LatLng futurePosition) {
+ if(futurePosition == null){
+ return ;
+ }
+
+ mCameraUpdate = CameraUpdateFactory.newLatLng(futurePosition);
+ executeRunnable(mThreadAnimate);
+
+ }
+
+ CancelableCallback mCancelCallback = new GoogleMap.CancelableCallback() {
+
+ public void onFinish() {
+
+ // only zoom until max zoom level, after that only move camera
+ LatLng futurePosition = mSanta.getFuturePosition(SantaPreferences
+ .getCurrentTime() + ANIMATION_DURATION);
+
+ if (futurePosition == null
+ || mMap.getCameraPosition().zoom <= MAX_ZOOM) {
+ animateShowSantaDestination(futurePosition);
+
+ } else {
+ // Animate to where Santa is going to be
+ animateFollowSanta(futurePosition);
+ }
+
+ executeRunnable(mThreadAnimate);
+ }
+
+ public void onCancel() {
+ }
+ };
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/MoveAroundSanta.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/MoveAroundSanta.java
new file mode 100644
index 000000000..551ff5d1e
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/MoveAroundSanta.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 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.cameraAnimations;
+
+import com.google.android.gms.maps.CameraUpdate;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.GoogleMap.CancelableCallback;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.map.SantaMarker;
+
+import android.os.Handler;
+
+/**
+ * Camera animation that centers on Santa, then follows his position.
+ *
+ * @author jfschmakeit
+ */
+public class MoveAroundSanta extends SantaCamAnimation {
+
+ private static final int ANIMATION_DURATION = 10000;
+ private static final int ANIMATION_CATCHUP_DURATION = 5000;
+
+ private static final float MAX_ZOOM = 10f;
+ private static final float MIN_ZOOM = 7.8f;
+ private static final float MAX_TILT = 40f;
+ private static final float MIN_TILT = 0f;
+
+ private static final int SCROLL_FRAME_DURATION = 300;
+
+ private static final int STATE_FULL = 1;
+ private static final int STATE_CATCHUP = 2;
+ private static final int STATE_CATCHUP_IN = 3;
+ private static final int STATE_SCROLL = 4;
+ private static final int STATE_SMALL = 5;
+ private static final int STATE_IN_ANIMATION = 6;
+
+ // Order: Full, catchup, scroll, small, catchup, scroll
+ private static final int[] ORDER = new int[]{
+ STATE_FULL, STATE_IN_ANIMATION, STATE_CATCHUP, STATE_CATCHUP_IN, STATE_SCROLL,
+ /*STATE_SMALL, STATE_IN_ANIMATION, */ STATE_CATCHUP, STATE_CATCHUP_IN, STATE_SCROLL
+ };
+
+ private int mState = 0;
+ private long mScrollFrames = 0;
+
+ private CameraUpdate mAnimateCameraUpdate;
+ private CameraUpdate mMoveCameraUpdate;
+ private int mAnimationDuration = ANIMATION_DURATION;
+
+ public MoveAroundSanta(Handler handler, GoogleMap mMap, SantaMarker mSanta) {
+ super(handler, mMap, mSanta);
+
+ reset();
+ }
+
+ public void reset() {
+ super.reset();
+ mState = 0;
+ mIsCancelled = false;
+ }
+
+ private long mAnimationStart;
+ private float mAnimationBearingChange;
+ private float mInitialBearing;
+
+ public void onSantaMoving(LatLng position) {
+ // only execute animation if not cancelled
+ // (required so that scroll won't be animated)
+ if (!mIsCancelled) {
+ switch (ORDER[mState]) {
+ case STATE_CATCHUP:
+ catchupAnimation();
+ nextState();
+ break;
+ case STATE_FULL:
+ fullAnimation();
+ nextState();
+ break;
+ case STATE_SMALL:
+ smallAnimation();
+ nextState();
+ break;
+ case STATE_CATCHUP_IN:
+ // ignore during catchup animation, heading does not change
+ break;
+ case STATE_IN_ANIMATION:
+ if (mAnimationStart > 0) {
+ updateHeading();
+ }
+ break;
+ case STATE_SCROLL:
+ if (mScrollFrames > SCROLL_FRAME_DURATION) {
+ nextState();
+ } else {
+ scrollAnimation(position);
+ mScrollFrames++;
+ }
+ break;
+ }
+ }
+ }
+
+ private void updateHeading() {
+
+ // never exceed progress, could be called with off-timings.
+ float p = Math.min(
+ ((float) (System.currentTimeMillis() - mAnimationStart))
+ / (float) ANIMATION_DURATION, 1f);
+
+ float b = mInitialBearing + (mAnimationBearingChange * p);
+ // Log.d(TAG, "progress=" + p +
+ // ", endB="+(mInitialBearing+mAnimationBearingChange)+", initialBearing:"
+ // + mInitialBearing + ", AnimbearingChange:"
+ // + mAnimationBearingChange);
+ if (b < 0f) {
+ b += 360f;
+ }
+ mSanta.setCameraOrientation(b);
+ }
+
+ private void nextState() {
+ mState = (mState + 1) % ORDER.length;
+ mScrollFrames = 0;
+ }
+
+ private void catchupAnimation() {
+ LatLng position = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ + ANIMATION_CATCHUP_DURATION);
+
+ mAnimationDuration = ANIMATION_CATCHUP_DURATION;
+ mAnimateCameraUpdate = CameraUpdateFactory.newLatLng(position);
+
+ executeRunnable(mThreadAnimate);
+ }
+
+ private void smallAnimation() {
+ LatLng pos = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ + ANIMATION_DURATION);
+ float tilt = SantaPreferences.getRandom(MIN_TILT, MAX_TILT);
+ float bearing = SantaPreferences.getRandom(0f, 306f);
+
+ CameraPosition camera = new CameraPosition.Builder().target(pos)
+ .tilt(tilt).zoom(mMap.getCameraPosition().zoom)
+ .bearing(bearing).build();
+
+ saveBearing(bearing);
+
+ mAnimationDuration = ANIMATION_DURATION;
+ mAnimateCameraUpdate = CameraUpdateFactory.newCameraPosition(camera);
+ executeRunnable(mThreadAnimate);
+ }
+
+ private void scrollAnimation(LatLng position) {
+ mMoveCameraUpdate = CameraUpdateFactory.newLatLng(position);
+ executeRunnable(mThreadMove);
+
+ }
+ private boolean skipScroll = false;
+
+ Runnable mThreadMove = new Runnable() {
+
+ public void run() {
+ if (mMap != null && mMoveCameraUpdate != null && !mIsCancelled && !skipScroll) {
+ mMap.moveCamera(mMoveCameraUpdate);
+ mAnimationStart = System.currentTimeMillis();
+ }
+ skipScroll = false;
+ }
+ };
+
+ private void fullAnimation() {
+ // get position in future so that camera is centered when camera
+ // animation is finished
+ LatLng pos = mSanta.getFuturePosition(SantaPreferences.getCurrentTime()
+ + ANIMATION_DURATION);
+ float tilt = SantaPreferences.getRandom(MIN_TILT, MAX_TILT);
+ float zoom = SantaPreferences.getRandom(MIN_ZOOM, MAX_ZOOM);
+ float bearing = SantaPreferences.getRandom(0f, 306f);
+
+ // store animation heading changes
+ saveBearing(bearing);
+
+ CameraPosition camera = new CameraPosition.Builder().target(pos)
+ .tilt(tilt).zoom(zoom).bearing(bearing).build();
+
+ mAnimationDuration = ANIMATION_DURATION;
+ mAnimateCameraUpdate = CameraUpdateFactory.newCameraPosition(camera);
+ executeRunnable(mThreadAnimate);
+
+ }
+
+ private void saveBearing(float endBearing) {
+ float startBearing = mMap.getCameraPosition().bearing;
+ if (mInitialBearing > endBearing) {
+ if (startBearing - endBearing > 180) {
+ startBearing -= 360f;
+ }
+ } else {
+ if (endBearing - startBearing > 180) {
+ endBearing -= 360f;
+ }
+ }
+ mInitialBearing = startBearing;
+ mAnimationBearingChange = endBearing - startBearing;
+
+ }
+
+ public void triggerPaddingAnimation(){
+ // Cancel the scroll animation
+ if(ORDER[mState] == STATE_SCROLL){
+ nextState();
+ skipScroll = true;
+ }
+
+ }
+
+ Runnable mThreadAnimate = new Runnable() {
+
+ public void run() {
+ if (mAnimateCameraUpdate != null) {
+ mMap.animateCamera(mAnimateCameraUpdate, mAnimationDuration,
+ mCancelListener);
+ mAnimationStart = System.currentTimeMillis();
+
+ }
+ }
+ };
+
+ CancelableCallback mCancelListener = new GoogleMap.CancelableCallback() {
+
+ public void onFinish() {
+ nextState();
+ }
+
+ public void onCancel() {
+
+ }
+ };
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimation.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimation.java
new file mode 100644
index 000000000..a844a42d6
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimation.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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.cameraAnimations;
+
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.apps.santatracker.map.SantaMarker;
+
+import android.os.Handler;
+
+/**
+ * An animation executed during Santa Cam mode.
+ *
+ * @author jfschmakeit
+ */
+public abstract class SantaCamAnimation {
+
+ //private static final String TAG = "SantaCamAnimation";
+ protected GoogleMap mMap;
+ protected SantaMarker mSanta;
+ private Handler mHandler;
+ protected boolean mIsCancelled;
+
+ public SantaCamAnimation(Handler mHandler, GoogleMap mMap,
+ SantaMarker mSanta) {
+ super();
+ this.mMap = mMap;
+ this.mSanta = mSanta;
+ this.mHandler = mHandler;
+ this.mIsCancelled = false;
+ }
+
+ public void reset() {
+ this.mIsCancelled = false;
+ }
+
+ public void cancel() {
+ // stop execution
+ this.mIsCancelled = true;
+ }
+
+ protected void executeRunnable(Runnable r) {
+ if (!mIsCancelled) {
+ mHandler.post(r);
+ }
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimator.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimator.java
new file mode 100644
index 000000000..9cf443d8d
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cameraAnimations/SantaCamAnimator.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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.cameraAnimations;
+
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.apps.santatracker.map.SantaMarker;
+
+import android.os.Handler;
+
+public class SantaCamAnimator {
+
+ private Handler mHandler;
+
+ private MoveAroundSanta mTravellingAnimation;
+
+ private CurrentPathAnimation mPathAnimation;
+
+ /**
+ * Frames to stay in departing animation
+ */
+ private static final int DEPARTING_TIME = 10000;
+
+ // Frames to wait for the current animation
+ private static final long ARRIVING_TRIGGER = 15000;
+
+ private boolean mStartedDeparting, mStartedArriving;
+
+ private boolean mPaused = false;
+
+ public SantaCamAnimator(GoogleMap mMap, SantaMarker mSantaMarker) {
+ super();
+ this.mHandler = new Handler();
+
+ // setup animations
+ mPathAnimation = new CurrentPathAnimation(mHandler, mMap, mSantaMarker);
+
+ mTravellingAnimation = new MoveAroundSanta(mHandler, mMap, mSantaMarker);
+
+ }
+
+ public void triggerPaddingAnimation(){
+ mTravellingAnimation.triggerPaddingAnimation();
+ }
+
+ public void animate(LatLng position, long remainingTime, long elapsedTime) {
+ if (!mPaused && position != null) {
+ if (!mStartedDeparting && elapsedTime < DEPARTING_TIME) {
+ // reset variables and show first departing animation
+ mPathAnimation.start();
+ mTravellingAnimation.reset();
+ mStartedArriving = false;
+ mStartedDeparting = true;
+ } else if (!mStartedArriving && remainingTime < ARRIVING_TRIGGER) {
+ // arriving animation
+ mPathAnimation.start();
+ mStartedArriving = true;
+
+ } else if (remainingTime >= ARRIVING_TRIGGER
+ && elapsedTime >= DEPARTING_TIME) {
+ // between departing and arriving times, animate travelling
+ // animation
+ mTravellingAnimation.onSantaMoving(position);
+
+ }
+ }
+ }
+
+ public void pause() {
+ mPaused = true;
+ }
+
+ public void resume() {
+ mPaused = false;
+ }
+
+ public void cancel() {
+ mTravellingAnimation.cancel();
+ mPathAnimation.cancel();
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ public void reset() {
+ mPathAnimation.reset();
+ mTravellingAnimation.reset();
+ mPaused = false;
+ mStartedDeparting = false;
+ mStartedArriving = false;
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardAdapter.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardAdapter.java
new file mode 100644
index 000000000..6acb7dff8
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardAdapter.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.text.Html;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.Destination;
+import com.google.android.apps.santatracker.data.StreamEntry;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.youtube.player.YouTubeInitializationResult;
+import com.google.android.youtube.player.YouTubeThumbnailLoader;
+import com.google.android.youtube.player.YouTubeThumbnailView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CardAdapter extends RecyclerView.Adapter {
+
+ private final String mYouTubeApiDeveloperKey;
+ private final Context mContext;
+
+ private boolean mDisableDestinationPhoto = true;
+
+ private final Typeface mTypefaceLabel;
+ private final Typeface mTypefaceBody;
+
+ private final CardAdapterListener mListener;
+ private final DestinationCardKeyListener mDestinationCardListener;
+
+ private final long mDashboardId;
+
+ private TvFocusAnimator mFocusHighlight;
+
+ public static final int DASHBOARD_POSITION = 0;
+
+ /**
+ * The list of cards. Note that the cards always need to be sorted by timestamps in the
+ * descending order.
+ */
+ private final ArrayList mCards = new ArrayList<>();
+ private final ThumbnailListener mThumbnailListener;
+ private final Map mThumbnailViewToLoader =
+ new HashMap<>();
+
+ private static final String TAG = "CardAdaptor";
+ private final boolean mIsTv;
+
+ public CardAdapter(Context context, CardAdapterListener listener) {
+ this(context, listener, null, false);
+ }
+
+ public CardAdapter(Context context, CardAdapterListener listener,
+ DestinationCardKeyListener destCardListener, boolean isTv) {
+ mContext = context;
+ mTypefaceLabel = Typeface.createFromAsset(context.getAssets(),
+ context.getResources().getString(R.string.typeface_robotocondensed_regular));
+ mTypefaceBody = Typeface.createFromAsset(context.getAssets(),
+ context.getResources().getString(R.string.typeface_roboto_light));
+ mListener = listener;
+ mDestinationCardListener = destCardListener;
+
+ TrackerCard.Dashboard dashboard = TrackerCard.Dashboard.getInstance();
+ mDashboardId = dashboard.id;
+ mCards.add(DASHBOARD_POSITION, dashboard);
+ mThumbnailListener = new ThumbnailListener();
+ mYouTubeApiDeveloperKey = context.getString(R.string.config_maps_api_key);
+ mIsTv = isTv;
+
+ setHasStableIds(true);
+ }
+
+ @Override
+ public CardViewHolder onCreateViewHolder(ViewGroup parent, int type) {
+ CardViewHolder holder;
+ switch (type) {
+ case TrackerCard.TYPE_DASHBOARD: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_dashboard, parent, false);
+ holder = new DashboardViewHolder(view);
+ break;
+ }
+ case TrackerCard.TYPE_FACTOID: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_factoid, parent, false);
+ holder = new FactoidViewHolder(view);
+ break;
+ }
+ case TrackerCard.TYPE_DESTINATION: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_destination, parent, false);
+ holder = new DestinationViewHolder(view);
+ break;
+ }
+ case TrackerCard.TYPE_PHOTO: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_photo, parent, false);
+ holder = new PhotoViewHolder(view);
+ break;
+ }
+ case TrackerCard.TYPE_MOVIE: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_movie, parent, false);
+ holder = new MovieViewHolder(view);
+ break;
+ }
+ case TrackerCard.TYPE_STATUS: {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.item_card_update, parent, false);
+ holder = new UpdateViewHolder(view);
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unexpected type of card.");
+ }
+ }
+ holder.setTypefaces(mTypefaceLabel, mTypefaceBody);
+ setupTvCardIfNecessary(holder);
+ return holder;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void setupTvCardIfNecessary(CardViewHolder holder) {
+ if (mIsTv) {
+
+ if (mFocusHighlight == null) {
+ mFocusHighlight = new TvFocusAnimator();
+ }
+
+ final Resources res = holder.itemView.getResources();
+
+ TvFocusAnimator.FocusChangeListener listener
+ = new TvFocusAnimator.FocusChangeListener(mFocusHighlight);
+
+ holder.itemView.setFocusable(true);
+ holder.itemView.setFocusableInTouchMode(false);
+ holder.itemView.setOnFocusChangeListener(listener);
+ holder.itemView.setElevation(res.getDimensionPixelOffset(R.dimen.toolbar_elevation));
+
+ mFocusHighlight.onInitializeView(holder.itemView);
+
+ if (holder.itemView.getBackground() == null) {
+ holder.itemView.setBackground(res.getDrawable(R.drawable.tv_tracker_card_selector));
+ }
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(CardViewHolder holder, int position) {
+ TrackerCard card = mCards.get(position);
+ if (card instanceof TrackerCard.Dashboard) {
+ ((DashboardViewHolder) holder).location.setSelected(true);
+ } else if (card instanceof TrackerCard.DestinationCard) {
+ TrackerCard.DestinationCard destination = (TrackerCard.DestinationCard) card;
+ DestinationViewHolder h = (DestinationViewHolder) holder;
+ Context context = h.itemView.getContext();
+ h.city.setText(destination.city);
+ if (destination.region == null) {
+ h.region.setVisibility(View.GONE);
+ } else {
+ h.region.setText(destination.region);
+ h.region.setVisibility(View.VISIBLE);
+ }
+ if (destination.url != null && destination.attributionHtml != null) {
+ // Image
+ Glide.with(context).load(destination.url).into(h.image);
+ // Attribution
+ h.copyright.setText(Html.fromHtml(destination.attributionHtml));
+ }
+ h.arrival.setText(DashboardFormats.formatTime(context, destination.timestamp));
+ if (destination.hasWeather) {
+ h.weatherLabel.setVisibility(View.VISIBLE);
+ h.weather.setVisibility(View.VISIBLE);
+ h.weather.setText(context.getString(R.string.weather_format,
+ String.valueOf((int) destination.tempC),
+ String.valueOf((int) destination.tempF)));
+ } else {
+ h.weatherLabel.setVisibility(View.GONE);
+ h.weather.setVisibility(View.GONE);
+ }
+
+ if (mIsTv) {
+ h.streetView.setVisibility(View.GONE);
+
+ if (destination.position != null) {
+ h.card.setTag(destination.position);
+ h.card.setOnKeyListener(mMoveToDestinationListener);
+ h.card.setClickable(true);
+ } else {
+ h.card.setTag(null);
+ h.card.setOnKeyListener(null);
+ h.card.setClickable(false);
+ }
+ } else {
+ if (destination.streetView != null) {
+ h.streetView.setVisibility(View.VISIBLE);
+ h.streetView.setOnClickListener(mShowStreetViewListener);
+ h.streetView.setTag(destination.streetView);
+ } else if (h.streetView != null) {
+ h.streetView.setVisibility(View.GONE);
+ }
+
+ }
+ } else if (card instanceof TrackerCard.FactoidCard) {
+ TrackerCard.FactoidCard factoid = (TrackerCard.FactoidCard) card;
+ FactoidViewHolder h = (FactoidViewHolder) holder;
+ h.body.setText(factoid.factoid);
+ } else if (card instanceof TrackerCard.PhotoCard) {
+ TrackerCard.PhotoCard photo = (TrackerCard.PhotoCard) card;
+ PhotoViewHolder h = (PhotoViewHolder) holder;
+ Glide.with(h.image.getContext()).load(photo.imageUrl).into(h.image);
+ } else if (card instanceof TrackerCard.MovieCard) {
+ TrackerCard.MovieCard movie = (TrackerCard.MovieCard) card;
+ MovieViewHolder h = (MovieViewHolder) holder;
+ h.thumbnail.setTag(movie.youtubeId);
+ if (mThumbnailViewToLoader.containsKey(h.thumbnail)) {
+ final YouTubeThumbnailLoader loader = mThumbnailViewToLoader.get(h.thumbnail);
+ if (loader != null) {
+ loader.setVideo(movie.youtubeId);
+ }
+ } else {
+ h.thumbnail.setImageDrawable(null);
+ h.thumbnail.initialize(mYouTubeApiDeveloperKey, mThumbnailListener);
+ mThumbnailViewToLoader.put(h.thumbnail, null);
+ }
+ h.play.setTag(movie.youtubeId);
+ h.play.setOnClickListener(mPlayVideoListener);
+ if (mIsTv) {
+ h.card.setTag(movie.youtubeId);
+ h.card.setOnClickListener(mPlayVideoListener);
+ }
+ } else if (card instanceof TrackerCard.StatusCard) {
+ TrackerCard.StatusCard status = (TrackerCard.StatusCard) card;
+ UpdateViewHolder h = (UpdateViewHolder) holder;
+ h.content.setText(status.status);
+ }
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ releaseLoaders();
+ super.onDetachedFromRecyclerView(recyclerView);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mCards.get(position).id;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mCards.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mCards.get(position).getType();
+ }
+
+ public long getDashboardId() {
+ return mDashboardId;
+ }
+
+ /**
+ * Find the right index to insert a new card with the specified {@code timestamp}. Note that
+ * this method assumes that {@link #mCards} are sorted by timestamps in the descending order.
+ *
+ * @param timestamp The unix time.
+ * @return The index to insert a new card in {@link #mCards}.
+ */
+ private int findCardIndexByTimestamp(long timestamp) {
+ // This is basically a binary search that doesn't search for a specific value but an index.
+ int head = 0;
+ int tail = mCards.size();
+ while (head < tail) {
+ int needle = (head + tail) / 2;
+ long needleTimestamp = mCards.get(needle).timestamp;
+ if (timestamp < needleTimestamp) {
+ head = needle + 1;
+ } else {
+ tail = needle;
+ }
+ }
+ return head;
+ }
+
+ private int addCard(TrackerCard card) {
+ final int index = findCardIndexByTimestamp(card.timestamp);
+ // Replace a old duplicated card if it exists.
+ if (index < mCards.size() && mCards.get(index).equals(card)) {
+ mCards.remove(index);
+ mCards.add(index, card);
+ notifyItemChanged(index);
+ } else {
+ mCards.add(index, card);
+ notifyItemInserted(index);
+ }
+ return index;
+ }
+
+ public int addDestination(boolean fromUser, Destination destination, boolean showStreetView) {
+ String url = null;
+ String copyright = null;
+ if (!mDisableDestinationPhoto && destination.photos != null
+ && destination.photos.length > 0) {
+ url = destination.photos[0].url;
+ copyright = destination.photos[0].attributionHTML;
+ }
+ Destination.StreetView streetView = null;
+ if (showStreetView && destination.streetView != null && destination.streetView.id != null
+ && !destination.streetView.id.isEmpty()) {
+ streetView = destination.streetView;
+ }
+ boolean hasWeather = destination.weather != null;
+ double tempC = 0, tempF = 0;
+ if (hasWeather) {
+ tempC = destination.weather.tempC;
+ tempF = destination.weather.tempF;
+ }
+ return addCard(new TrackerCard.DestinationCard(destination.arrival, destination.position,
+ fromUser, destination.city, destination.region,
+ url, copyright, streetView, hasWeather, tempC, tempF));
+ }
+
+ public TrackerCard addStreamEntry(StreamEntry entry) {
+ TrackerCard card = null;
+ if (entry.didYouKnow != null) {
+ // Did you know card
+ card = new TrackerCard.FactoidCard(entry.timestamp, entry.didYouKnow);
+ addCard(card);
+ } else if (entry.santaStatus != null) {
+ // Status card
+ card = new TrackerCard.StatusCard(entry.timestamp, entry.santaStatus);
+ addCard(card);
+ } else if (entry.image != null) {
+ // Image card
+ card = new TrackerCard.PhotoCard(entry.timestamp, entry.image, entry.caption);
+ addCard(card);
+ } else if (entry.video != null) {
+ // Video card
+ card = new TrackerCard.MovieCard(entry.timestamp, entry.video);
+ addCard(card);
+ }
+ return card;
+ }
+
+ public void setNextLocation(String nextLocation) {
+ TrackerCard.Dashboard.getInstance().nextDestination = nextLocation;
+ }
+
+ public void setDestinationPhotoDisabled(boolean disablePhoto) {
+ mDisableDestinationPhoto = disablePhoto;
+ }
+
+ private final class ThumbnailListener implements
+ YouTubeThumbnailView.OnInitializedListener,
+ YouTubeThumbnailLoader.OnThumbnailLoadedListener {
+
+ @Override
+ public void onInitializationSuccess(
+ YouTubeThumbnailView view, YouTubeThumbnailLoader loader) {
+ loader.setOnThumbnailLoadedListener(this);
+ view.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ loader.setVideo((String) view.getTag());
+ mThumbnailViewToLoader.put(view, loader);
+ }
+
+ @Override
+ public void onInitializationFailure(
+ YouTubeThumbnailView view, YouTubeInitializationResult loader) {
+ SantaLog.e(TAG, "Failed to initialize YouTubeThumbnailView.");
+ view.setImageResource(R.drawable.big_play_button);
+ view.setScaleType(ImageView.ScaleType.CENTER);
+ }
+
+ @Override
+ public void onThumbnailLoaded(YouTubeThumbnailView view, String videoId) {
+ }
+
+ @Override
+ public void onThumbnailError(YouTubeThumbnailView view, YouTubeThumbnailLoader.ErrorReason errorReason) {
+ Log.e(TAG, "Failed to load YouTubThumbnail");
+ SantaLog.e(TAG, errorReason.toString());
+ view.setImageResource(R.drawable.big_play_button);
+ }
+ }
+
+ public interface DestinationCardKeyListener {
+
+ void onJumpToDestination(LatLng destination);
+ void onFinish();
+ boolean onMoveBy(KeyEvent event);
+ }
+
+ public interface CardAdapterListener {
+
+ void onOpenStreetView(Destination.StreetView streetView);
+
+ void onPlayVideo(String youtubeId);
+ }
+
+ private View.OnClickListener mShowStreetViewListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ mListener.onOpenStreetView((Destination.StreetView) v.getTag());
+ }
+
+ };
+
+ private View.OnClickListener mPlayVideoListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ mListener.onPlayVideo((String) v.getTag());
+ }
+
+ };
+
+ private View.OnKeyListener mMoveToDestinationListener = new View.OnKeyListener() {
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+
+ if (mDestinationCardListener == null) {
+ return false;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_BUTTON_A) {
+
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ mDestinationCardListener.onJumpToDestination((LatLng) v.getTag());
+ break;
+ case KeyEvent.ACTION_UP:
+ mDestinationCardListener.onFinish();
+ break;
+ }
+ return false;
+ }
+
+ // When a DPAD is pressed, fire an 'onMove' event.
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ //fall through
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ //fall through
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ //fall through
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return mDestinationCardListener.onMoveBy(event);
+ }
+
+ return false;
+ }
+ };
+
+
+ public void releaseLoaders() {
+ for (YouTubeThumbnailLoader loader : mThumbnailViewToLoader.values()) {
+ loader.release();
+ }
+ mThumbnailViewToLoader.clear();
+ }
+
+}
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardViewHolder.java
new file mode 100644
index 000000000..21a1a053e
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/CardViewHolder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.Typeface;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+
+abstract class CardViewHolder extends RecyclerView.ViewHolder {
+
+ public View card;
+
+ public CardViewHolder(View itemView) {
+ super(itemView);
+ card = itemView.findViewById(R.id.card);
+ }
+
+ protected static void setTypeface(TextSwitcher[] switchers, Typeface typeface) {
+ for (TextSwitcher textSwitcher : switchers) {
+ for (int i = 0; i < textSwitcher.getChildCount(); i++) {
+ TextView tv = (TextView) textSwitcher.getChildAt(i);
+ tv.setTypeface(typeface);
+ }
+ }
+ }
+
+ protected static void setTypeface(TextView[] views, Typeface typeface) {
+ for (TextView textView : views) {
+ textView.setTypeface(typeface);
+ }
+ }
+
+ public abstract void setTypefaces(Typeface label, Typeface body);
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardFormats.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardFormats.java
new file mode 100644
index 000000000..f11fd025c
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardFormats.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.content.Context;
+
+import com.google.android.apps.santatracker.data.Destination;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+
+import java.text.NumberFormat;
+import java.util.GregorianCalendar;
+
+public class DashboardFormats {
+
+ private static final NumberFormat PRESENTS_COUNT_FORMAT = NumberFormat.getIntegerInstance();
+ private static final String TIME_FORMAT = "%02d:%02d";
+ private static final String COUNTDOWN_HMS = "%d:%02d:%02d";
+ private static final String COUNTDOWN_MS = "%02d:%02d";
+ private static GregorianCalendar sCalendar;
+ private static Long sOffset;
+
+ public static String formatDestination(Destination d) {
+ if (d != null) {
+ return d.getPrintName();
+ }
+ return null;
+ }
+
+ public static String formatPresents(long presents) {
+ return PRESENTS_COUNT_FORMAT.format(presents);
+ }
+
+ /**
+ * Format the given unix time. This is not thread-safe.
+ *
+ * @param context The context.
+ * @param timestamp The unix time.
+ * @return A formatted string.
+ */
+ public static String formatTime(Context context, long timestamp) {
+ if (sCalendar == null) {
+ sCalendar = (GregorianCalendar) GregorianCalendar.getInstance();
+ }
+ if (sOffset == null) {
+ sOffset = new SantaPreferences(context).getOffset();
+ }
+ sCalendar.setTimeInMillis(timestamp - sOffset);
+ return String.format(TIME_FORMAT, sCalendar.get(GregorianCalendar.HOUR),
+ sCalendar.get(GregorianCalendar.MINUTE));
+ }
+
+ /**
+ * @param time The time in milliseconds
+ * @return
+ */
+ public static String formatCountdown(long time) {
+ final int iHours = (int) Math.floor(time / (60 * 60 * 1000) % 24);
+ final int iMinutes = (int) Math.floor(time / (60 * 1000) % 60);
+ final int iSeconds = (int) Math.floor(time / (1000) % 60);
+ if (iHours > 0) {
+ return String.format(COUNTDOWN_HMS, iHours, iMinutes, iSeconds);
+ } else {
+ return String.format(COUNTDOWN_MS, iMinutes, iSeconds);
+ }
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardViewHolder.java
new file mode 100644
index 000000000..f03762790
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DashboardViewHolder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import com.google.android.apps.santatracker.R;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+public class DashboardViewHolder extends CardViewHolder {
+
+ public TextView locationLabel, countdownLabel;
+ public TextSwitcher location, presents, countdown;
+ public ViewGroup presentsContainer, countdownContainer;
+
+ public DashboardViewHolder(View itemView) {
+ super(itemView);
+ locationLabel = (TextView) itemView.findViewById(R.id.dash_location_label);
+ location = (TextSwitcher) itemView.findViewById(R.id.dash_location);
+ presentsContainer = (ViewGroup) itemView.findViewById(R.id.dash_presents_container);
+ presents = (TextSwitcher) itemView.findViewById(R.id.dash_presents);
+ countdownContainer = (ViewGroup) itemView.findViewById(R.id.dash_countdown_container);
+ countdownLabel = (TextView) itemView.findViewById(R.id.dash_countdown_label);
+ countdown = (TextSwitcher) itemView.findViewById(R.id.dash_countdown);
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface text) {
+ setTypeface(new TextSwitcher[]{location, presents, countdown}, text);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DestinationViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DestinationViewHolder.java
new file mode 100644
index 000000000..a3592d889
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/DestinationViewHolder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.PorterDuff;
+import android.graphics.Typeface;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+
+public class DestinationViewHolder extends CardViewHolder {
+
+ public TextView region;
+ public TextView city;
+ public TextView copyright;
+ public TextView arrival;
+ public TextView arrivalLabel;
+ public TextView weather;
+ public TextView weatherLabel;
+ public ImageView image;
+ public Button streetView;
+
+ public DestinationViewHolder(View itemView) {
+ super(itemView);
+ region = (TextView) itemView.findViewById(R.id.destination_region);
+ city = (TextView) itemView.findViewById(R.id.destination_city);
+ copyright = (TextView) itemView.findViewById(R.id.destination_copyright);
+ arrival = (TextView) itemView.findViewById(R.id.destination_arrival);
+ arrivalLabel = (TextView) itemView.findViewById(R.id.destination_arrival_label);
+ weather = (TextView) itemView.findViewById(R.id.destination_weather);
+ weatherLabel = (TextView) itemView.findViewById(R.id.destination_weather_label);
+ image = (ImageView) itemView.findViewById(R.id.destination_image);
+ streetView = (Button) itemView.findViewById(R.id.destination_street_view);
+
+ image.setColorFilter(itemView.getResources().getColor(R.color.overlayDestinationCardFilter),
+ PorterDuff.Mode.MULTIPLY);
+
+ copyright.setMovementMethod(new LinkMovementMethod());
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface body) {
+ setTypeface(new TextView[]{copyright, arrival, weather}, body);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/FactoidViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/FactoidViewHolder.java
new file mode 100644
index 000000000..e9b5b3b92
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/FactoidViewHolder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+
+public class FactoidViewHolder extends CardViewHolder {
+
+ public TextView body;
+
+ public FactoidViewHolder(View itemView) {
+ super(itemView);
+ body = (TextView) itemView.findViewById(R.id.factoid_text);
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface text) {
+ setTypeface(new TextView[]{(TextView) itemView.findViewById(R.id.factoid_attribution)},
+ label);
+ setTypeface(new TextView[]{body}, text);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/MovieViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/MovieViewHolder.java
new file mode 100644
index 000000000..4d77b76bd
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/MovieViewHolder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.widget.Button;
+
+import com.google.android.youtube.player.YouTubeThumbnailView;
+
+import com.google.android.apps.santatracker.R;
+
+public class MovieViewHolder extends CardViewHolder {
+
+ public YouTubeThumbnailView thumbnail;
+ public Button play;
+
+ public MovieViewHolder(View itemView) {
+ super(itemView);
+ thumbnail = (YouTubeThumbnailView) itemView.findViewById(R.id.movie_play);
+ play = (Button) itemView.findViewById(R.id.destination_movie_play);
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface body) {
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/PhotoViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/PhotoViewHolder.java
new file mode 100644
index 000000000..a899da319
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/PhotoViewHolder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.google.android.apps.santatracker.R;
+
+public class PhotoViewHolder extends CardViewHolder {
+
+ public ImageView image;
+
+ public PhotoViewHolder(View itemView) {
+ super(itemView);
+ image = (ImageView) itemView.findViewById(R.id.photo_image);
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface body) {
+ // Do nothing
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/SeparatorDecoration.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/SeparatorDecoration.java
new file mode 100644
index 000000000..c5f1cc217
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/SeparatorDecoration.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.google.android.apps.santatracker.R;
+
+public class SeparatorDecoration extends RecyclerView.ItemDecoration {
+
+ private final Drawable mDrawable;
+ private final int mHeight;
+
+ public SeparatorDecoration(Context context) {
+ mDrawable = ContextCompat.getDrawable(context, R.drawable.stream_separator);
+ mHeight = context.getResources().getDimensionPixelSize(R.dimen.separator_height);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getBottom() + params.bottomMargin;
+ final int bottom = top + mHeight;
+ mDrawable.setBounds(left, top, right, bottom);
+ mDrawable.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.set(0, 0, 0, mHeight);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TrackerCard.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TrackerCard.java
new file mode 100644
index 000000000..7913e5a45
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TrackerCard.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import com.google.android.apps.santatracker.data.Destination;
+import com.google.android.gms.maps.model.LatLng;
+
+public abstract class TrackerCard {
+
+ public static final int TYPE_DASHBOARD = 1;
+ public static final int TYPE_FACTOID = 2; // Did you know...
+ public static final int TYPE_DESTINATION = 3;
+ public static final int TYPE_PHOTO = 4;
+ public static final int TYPE_MOVIE = 5;
+ public static final int TYPE_STATUS = 6;
+
+ private static long sMaxId;
+
+ public final long id;
+ public final long timestamp;
+
+ public TrackerCard(long timestamp) {
+ this.id = ++sMaxId;
+ this.timestamp = timestamp;
+ }
+
+ public abstract int getType();
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TrackerCard)) {
+ return false;
+ }
+ TrackerCard card = (TrackerCard) o;
+ return this.timestamp == card.timestamp && this.getType() == card.getType();
+ }
+
+ public static class Dashboard extends TrackerCard {
+
+ private static Dashboard sInstance = new Dashboard(Long.MAX_VALUE);
+
+ public String nextDestination;
+
+ /**
+ * This card has a timestamp of {@link Long#MAX_VALUE}, which makes sure that the dashboard
+ * is always at the top of the card list.
+ */
+ public static Dashboard getInstance() {
+ return sInstance;
+ }
+
+ private Dashboard(long timestamp) {
+ super(timestamp);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_DASHBOARD;
+ }
+ }
+
+ public static class FactoidCard extends TrackerCard {
+ public String factoid;
+
+ public FactoidCard(long timestamp, String didYouKnow) {
+ super(timestamp);
+ this.factoid = didYouKnow;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_FACTOID;
+ }
+ }
+
+ public static class DestinationCard extends TrackerCard {
+
+ public final boolean fromUser;
+ public final String city;
+ public final String region;
+ public final String url;
+ public final Destination.StreetView streetView;
+ public final String attributionHtml;
+ public final boolean hasWeather;
+ public final double tempC;
+ public final double tempF;
+ public LatLng position;
+
+ public DestinationCard(long timestamp, LatLng position, boolean fromUser, String city, String region,
+ String url, String attributionHtml,
+ Destination.StreetView streetview, boolean hasWeather,
+ double tempC, double tempF) {
+ super(timestamp);
+ this.fromUser = fromUser;
+ this.city = city;
+ this.region = region;
+ this.url = url;
+ this.attributionHtml = attributionHtml;
+ this.position = position;
+ this.streetView = streetview;
+ this.hasWeather = hasWeather;
+ this.tempC = tempC;
+ this.tempF = tempF;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_DESTINATION;
+ }
+ }
+
+ public static class PhotoCard extends TrackerCard {
+
+ public String imageUrl;
+ public String caption;
+
+ public PhotoCard(long timestamp, String image, String caption) {
+ super(timestamp);
+ this.imageUrl = image;
+ this.caption = caption;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+ }
+
+ public static class MovieCard extends TrackerCard {
+
+ public String youtubeId;
+
+ public MovieCard(long timestamp, String video) {
+ super(timestamp);
+ this.youtubeId = video;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_MOVIE;
+ }
+ }
+
+ public static class StatusCard extends TrackerCard {
+
+ public String status;
+
+ public StatusCard(long timestamp, String santaStatus) {
+ super(timestamp);
+ this.status = santaStatus;
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_STATUS;
+ }
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TvFocusAnimator.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TvFocusAnimator.java
new file mode 100644
index 000000000..6089fc955
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/TvFocusAnimator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.cardstream;
+
+import android.animation.TimeAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.support.v17.leanback.R;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * Animators for highlighting behavior when an item gains focus.
+ */
+public class TvFocusAnimator {
+
+ private static final int DURATION_MS = 150;
+
+ public void onItemFocused(View view, boolean hasFocus) {
+ view.setSelected(hasFocus);
+ getOrCreateAnimator(view).animateFocus(hasFocus, false);
+ }
+
+ public void onInitializeView(View view) {
+ getOrCreateAnimator(view).animateFocus(false, true);
+ }
+
+ private FocusAnimator getOrCreateAnimator(View view) {
+ FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
+ if (animator == null) {
+ final float scale = view.getResources().getFraction(
+ R.fraction.lb_focus_zoom_factor_xsmall, 1, 1);
+ animator = new FocusAnimator(view, scale,DURATION_MS);
+ view.setTag(R.id.lb_focus_animator, animator);
+ }
+ return animator;
+ }
+
+ public static final class FocusChangeListener implements View.OnFocusChangeListener {
+
+ final private TvFocusAnimator mAnimator;
+
+ public FocusChangeListener(TvFocusAnimator animator) {
+ mAnimator = animator;
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ mAnimator.onItemFocused(view, hasFocus);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ static class FocusAnimator implements TimeAnimator.TimeListener {
+ private final View mView;
+ private final int mDuration;
+ private final float mScaleDiff;
+ private final TimeAnimator mAnimator = new TimeAnimator();
+ private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+ private float mFocusLevel = 0f;
+ private float mFocusLevelStart;
+ private float mFocusLevelDelta;
+
+ FocusAnimator(View view, float scale, int duration) {
+ mView = view;
+ mDuration = duration;
+ mScaleDiff = scale - 1f;
+ mAnimator.setTimeListener(this);
+ }
+
+ void animateFocus(boolean select, boolean immediate) {
+ endAnimation();
+ final float end = select ? 1 : 0;
+ if (immediate) {
+ setFocusLevel(end);
+ } else if (mFocusLevel != end) {
+ mFocusLevelStart = mFocusLevel;
+ mFocusLevelDelta = end - mFocusLevelStart;
+ mAnimator.start();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ void setFocusLevel(float level) {
+ mFocusLevel = level;
+ float scale = 1f + mScaleDiff * level;
+ mView.setElevation(5 * scale);
+ mView.setScaleX(scale);
+ mView.setScaleY(scale);
+ }
+
+ void endAnimation() {
+ mAnimator.end();
+ }
+
+ @Override
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ float fraction;
+ if (totalTime >= mDuration) {
+ fraction = 1;
+ mAnimator.end();
+ } else {
+ fraction = (float) (totalTime / (double) mDuration);
+ }
+ if (mInterpolator != null) {
+ fraction = mInterpolator.getInterpolation(fraction);
+ }
+ setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
+ }
+ }
+}
\ No newline at end of file
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/UpdateViewHolder.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/UpdateViewHolder.java
new file mode 100644
index 000000000..2f9f15be4
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/map/cardstream/UpdateViewHolder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 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.cardstream;
+
+import android.graphics.Typeface;
+import android.view.View;
+import android.widget.TextView;
+
+import com.google.android.apps.santatracker.R;
+
+public class UpdateViewHolder extends CardViewHolder {
+
+ public TextView content;
+
+ public UpdateViewHolder(View itemView) {
+ super(itemView);
+ content = (TextView) itemView.findViewById(R.id.update_text);
+ }
+
+ @Override
+ public void setTypefaces(Typeface label, Typeface body) {
+ setTypeface(new TextView[]{content}, body);
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/APIProcessor.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/APIProcessor.java
new file mode 100644
index 000000000..b22df223f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/APIProcessor.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.data.StreamDbHelper;
+import com.google.android.apps.santatracker.data.Switches;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Abstracts access to the Santa API.
+ * This class handles the processing and interpretation of received data from the API, including
+ * parsing the result into {@link org.json.JSONObject}s and calling the
+ * {@link com.google.android.apps.santatracker.service.APIProcessor.APICallback}
+ * with any changed values.
+ */
+public abstract class APIProcessor {
+
+ private static final String TAG = "SantaCommunicator";
+
+ // JSON field names for parsing
+ protected final static String FIELD_ROUTEOFFSET = "routeOffset";
+ protected final static String FIELD_NOW = "now";
+ protected final static String FIELD_TIMEOFFSET = "timeOffset";
+ protected final static String FIELD_FINGERPRINT = "fingerprint";
+ protected final static String FIELD_SWITCHOFF = "switchOff";
+ protected final static String FIELD_REFRESH = "refresh";
+ protected final static String FIELD_DISABLE_CASTBUTTON = "DisableCastButton";
+ protected final static String FIELD_DISABLE_PHOTO = "DisableDestinationPhoto";
+ protected final static String FIELD_DISABLE_GUMBALLGAME = "DisableGumballGame";
+ protected final static String FIELD_DISABLE_JETPACKGAME = "DisableJetpackGame";
+ protected final static String FIELD_DISABLE_MEMORYGAME = "DisableMemoryGame";
+ protected final static String FIELD_DISABLE_ROCKETGAME = "DisableRocketGame";
+ protected final static String FIELD_DISABLE_DANCERGAME = "DisableDancerGame";
+ protected final static String FIELD_DISABLE_SNOWDOWNGAME = "DisableSnowdownGame";
+ protected final static String FIELD_VIDEO_1 = "Video1";
+ protected final static String FIELD_VIDEO_15 = "Video15";
+ protected final static String FIELD_VIDEO_23 = "Video23";
+
+ protected static final String FIELD_DESTINATIONS = "destinations";
+ protected static final String FIELD_STATUS = "status";
+
+ protected static final String FIELD_IDENTIFIER = "id";
+ protected static final String FIELD_ARRIVAL = "arrival";
+ protected static final String FIELD_DEPARTURE = "departure";
+
+ protected static final String FIELD_DETAILS_CITY = "city";
+ protected static final String FIELD_DETAILS_REGION = "region";
+ protected static final String FIELD_DETAILS_COUNTRY = "country";
+
+ protected static final String FIELD_DETAILS_LOCATION = "location";
+ protected static final String FIELD_DETAILS_LOCATION_LAT = "lat";
+ protected static final String FIELD_DETAILS_LOCATION_LNG = "lng";
+ protected static final String FIELD_DETAILS_PRESENTSDELIVERED = "presentsDelivered";
+
+ protected static final String FIELD_DETAILS_DETAILS = "details";
+ protected static final String FIELD_DETAILS_ALTITUDE = "altitude";
+ protected static final String FIELD_DETAILS_TIMEZONE = "timezone";
+ protected static final String FIELD_DETAILS_PHOTOS = "photos";
+ protected static final String FIELD_DETAILS_WEATHER = "weather";
+ protected static final String FIELD_DETAILS_STREETVIEW = "streetView";
+ protected static final String FIELD_DETAILS_GMMSTREETVIEW = "gmmStreetView";
+
+ public static final String FIELD_PHOTO_URL = "url";
+ public static final String FIELD_PHOTO_ATTRIBUTIONHTML= "attributionHtml";
+
+ public static final String FIELD_WEATHER_URL = "url";
+ public static final String FIELD_WEATHER_TEMPC = "tempC";
+ public static final String FIELD_WEATHER_TEMPF = "tempF";
+
+ public static final String FIELD_STREETVIEW_ID = "id";
+ public static final String FIELD_STREETVIEW_LATITUDE = "latitude";
+ public static final String FIELD_STREETVIEW_LONGITUDE = "longitude";
+ public static final String FIELD_STREETVIEW_HEADING = "heading";
+
+ public static final String FIELD_STREAM = "stream";
+ public static final String FIELD_STREAMOFFSET = "streamOffset";
+ public static final String FIELD_NOTIFICATIONSTREAM = "notificationStream";
+ public static final String FIELD_STREAM_TIMESTAMP = "timestamp";
+ public static final String FIELD_STREAM_STATUS = "status";
+ public static final String FIELD_STREAM_DIDYOUKNOW = "didyouknow";
+ public static final String FIELD_STREAM_IMAGEURL = "imageUrl";
+ public static final String FIELD_STREAM_YOUTUBEID = "youtubeId";
+
+ protected static final String FIELD_STATUS_OK = "OK";
+
+ public static final long ERROR_CODE = Long.MIN_VALUE;
+
+ private static final String EMPTY_STRING = "";
+
+ // Preferences
+ protected SantaPreferences mPreferences;
+ // DB helpers
+ protected DestinationDbHelper mDestinationDBHelper = null;
+ protected StreamDbHelper mStreamDBHelper = null;
+
+ // Callback
+ private APICallback mCallback;
+
+ public APIProcessor(SantaPreferences mPreferences, DestinationDbHelper mDBHelper,
+ StreamDbHelper mStreamDBHelper, APICallback mCallback) {
+ this.mPreferences = mPreferences;
+ this.mDestinationDBHelper = mDBHelper;
+ this.mStreamDBHelper = mStreamDBHelper;
+ this.mCallback = mCallback;
+ }
+
+ /**
+ * Load data from the API from the given URL and parse the returned data into a JSONObject.
+ *
+ * Implementations may ignore the URL parameter.
+ */
+ protected abstract JSONObject loadApi(String url);
+
+ /** Loads remotely configurable switches and flags. */
+ protected abstract Switches getSwitches();
+
+ /**
+ * Access the API from a URL and process its data.
+ * If any values have changed, the appropriate callbacks in
+ * {@link com.google.android.apps.santatracker.service.APIProcessor.APICallback} are called.
+ * Returns {@link #ERROR_CODE} if the data could not be loaded or processed.
+ * Returns the delay to the next API access if the access was successful.
+ */
+ public long accessAPI(String url) {
+ SantaLog.d(TAG, "URL=" + url);
+ // Get current values from mPreferences
+ long offsetPref = mPreferences.getOffset();
+ String fingerprintPref = mPreferences.getFingerprint();
+ boolean switchOffPref = mPreferences.getSwitchOff();
+ // client specific
+ String video1Pref = mPreferences.getVideos()[0];
+ String video15Pref = mPreferences.getVideos()[1];
+ String video23Pref = mPreferences.getVideos()[2];
+ boolean disableCastPref = mPreferences.getCastDisabled();
+ boolean disablePhotoPref = mPreferences.getDestinationPhotoDisabled();
+
+ boolean disableGumballPref = mPreferences.getGumballDisabled();
+ boolean disableJetpackPref = mPreferences.getJetpackDisabled();
+ boolean disableMemoryPref = mPreferences.getMemoryDisabled();
+ boolean disableRocketPref = mPreferences.getRocketDisabled();
+ boolean disableDancerPref = mPreferences.getDancerDisabled();
+ boolean disableSnowdownPref = mPreferences.getSnowdownDisabled();
+
+ // load data as JSON
+ JSONObject json = loadApi(url);
+
+ if (json == null) {
+ Log.d(TAG, "Santa Communication Error 3");
+ return ERROR_CODE;
+ }
+
+ try {
+ // Error if the status is not OK
+ if (!FIELD_STATUS_OK.equals(json.getString(FIELD_STATUS))) {
+ Log.d(TAG, "Santa Communication Error 4");
+ return ERROR_CODE;
+ }
+
+ final int routeOffset = json.getInt(FIELD_ROUTEOFFSET);
+ final long now = json.getLong(FIELD_NOW);
+ final long offset = json.getLong(FIELD_TIMEOFFSET);
+ final String fingerprint = json.getString(FIELD_FINGERPRINT);
+ final long refresh = json.getLong(FIELD_REFRESH);
+ final boolean switchOff = json.getBoolean(FIELD_SWITCHOFF);
+ final JSONArray locations = json.getJSONArray(FIELD_DESTINATIONS);
+
+ final int streamOffset = json.getInt(FIELD_STREAMOFFSET);
+ final JSONArray stream = json.getJSONArray(FIELD_STREAM);
+
+ // Notification stream parameters are optional
+ final JSONArray notificationStream =
+ json.has(FIELD_NOTIFICATIONSTREAM) ?
+ json.getJSONArray(FIELD_NOTIFICATIONSTREAM) : null;
+
+ // Fingerprint has changed, remove route and stream from db
+ if (!fingerprint.equals(fingerprintPref)) {
+ mCallback.notifyRouteUpdating();
+ //empty the database and reset preferences
+ mDestinationDBHelper.emptyDestinationTable();
+ mStreamDBHelper.emptyCardTable();
+ mPreferences.invalidateData();
+ }
+
+ // Destinations
+ if (locations != null && locations.length() > 0) {
+ int processedLocations = processRoute(locations);
+ if (processedLocations > 0) {
+ final int newOffset = routeOffset + processedLocations;
+ mCallback.onNewRouteLoaded();
+ mPreferences.setFingerprint(fingerprint);
+ mPreferences.setRouteOffset(newOffset);
+ SantaLog.d(TAG,
+ "Processed route - new details: " + newOffset + ", " + fingerprint);
+ }
+ }
+
+ // Stream
+ if (stream != null && stream.length() > 0) {
+ // process non-notification cards
+ int processedCards = processStream(stream, false);
+ if (processedCards > 0) {
+ final int newOffset = streamOffset + processedCards;
+ mCallback.onNewStreamLoaded();
+ mPreferences.setStreamOffset(newOffset);
+ SantaLog.d(TAG,
+ "Processed stream - new details: " + newOffset);
+ }
+ }
+
+ // Notification Stream
+ if (notificationStream != null && notificationStream.length() > 0) {
+ // process notification cards
+ int processedCards = processStream(notificationStream, true);
+ if (processedCards > 0) {
+ mCallback.onNewNotificationStreamLoaded();
+ SantaLog.d(TAG,
+ "Processed notification stream - count: " + processedCards);
+ }
+ }
+
+ // Offset
+ final long newOffset = now - System.currentTimeMillis() + offset;
+ if (offsetPref != newOffset) {
+ mPreferences.setOffset(newOffset);
+
+ SantaLog.d(TAG,
+ "New offset: " + newOffset + ", current=" + System.currentTimeMillis()
+ + ", new Santa=" + SantaPreferences.getCurrentTime());
+
+ // Log.d(TAG, "new offset: new="+newOffset+", now="+now+", offset="+offset+",
+ // prefOffset="+offsetPref+", time="+System.currentTimeMillis());
+ // Notify only if offset varies significantly
+ if ((newOffset > offsetPref + SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE
+ || newOffset
+ < offsetPref - SantaPreferences.OFFSET_ACCEPTABLE_RANGE_DIFFERENCE)) {
+ mCallback.onNewOffset();
+ }
+
+ }
+
+ if (switchOffPref != switchOff) {
+ mPreferences.setSwitchOff(switchOff);
+ mCallback.onNewSwitchOffState(switchOff);
+
+ }
+
+ // clientSpecific
+ Switches s = getSwitches();
+
+ if (disableCastPref != s.disableCastButton) {
+ // set cast preference
+ mPreferences.setCastDisabled(s.disableCastButton);
+ mCallback.onNewCastState(s.disableCastButton);
+ }
+
+ if (disablePhotoPref != s.disableDestinationPhoto) {
+ // set destination photo preference
+ mPreferences.setDestinationPhotoDisabled(s.disableDestinationPhoto);
+ mCallback.onNewDestinationPhotoState(s.disableDestinationPhoto);
+ }
+
+ // Games
+ if ((s.disableGumballGame != disableGumballPref)
+ || (s.disableJetpackGame != disableJetpackPref)
+ || (s.disableMemoryGame != disableMemoryPref)
+ || (s.disableRocketGame != disableRocketPref)
+ || (s.disableDancerGame != disableDancerPref)
+ || (s.disableSnowdownGame != disableSnowdownPref)) {
+ // set game preferences
+ mPreferences.setGamesDisabled(s.disableGumballGame, s.disableJetpackGame,
+ s.disableMemoryGame, s.disableRocketGame, s.disableDancerGame,
+ s.disableSnowdownGame);
+ mCallback.onNewGameState(s.disableGumballGame, s.disableJetpackGame,
+ s.disableMemoryGame, s.disableRocketGame, s.disableDancerGame,
+ s.disableSnowdownGame);
+ }
+
+ if ((s.video1 != null && s.video15 != null && s.video23 != null)
+ && ((!s.video1.equals(video1Pref)) || (!s.video15.equals(video15Pref))
+ || (!s.video23.equals(video23Pref)))) {
+ mPreferences.setVideos(s.video1, s.video15, s.video23);
+ mCallback.onNewVideos(s.video1, s.video15, s.video23);
+ }
+
+ if (!fingerprint.equals(fingerprintPref)) {
+ // new data has been processed and locations have been stored
+ mCallback.onNewFingerprint();
+ }
+
+ return refresh;
+ } catch (JSONException e) {
+ Log.d(TAG, "Santa Communication Error 5");
+ SantaLog.d(TAG, "JSON Exception", e);
+ return ERROR_CODE;
+ }
+
+ }
+
+ private int processRoute(JSONArray json) {
+ SQLiteDatabase db = mDestinationDBHelper.getWritableDatabase();
+
+ db.beginTransaction();
+ try {
+ // loop over each destination
+ long previousPresents = mPreferences.getTotalPresents();
+
+ int i;
+ for (i = 0; i < json.length(); i++) {
+ JSONObject dest = json.getJSONObject(i);
+
+ JSONObject location = dest
+ .getJSONObject(FIELD_DETAILS_LOCATION);
+
+ long presentsTotal = dest
+ .getLong(FIELD_DETAILS_PRESENTSDELIVERED);
+ long presents = presentsTotal - previousPresents;
+ previousPresents = presentsTotal;
+
+ // Name
+ String city = dest.getString(FIELD_DETAILS_CITY);
+ String region = null;
+ String country = null;
+
+ if (dest.has(FIELD_DETAILS_REGION)) {
+ region = dest.getString(FIELD_DETAILS_REGION);
+ if (region.length() < 1) {
+ region = null;
+ }
+ }
+ if (dest.has(FIELD_DETAILS_COUNTRY)) {
+ country = dest.getString(FIELD_DETAILS_COUNTRY);
+ if (country.length() < 1) {
+ country = null;
+ }
+ }
+
+// if (mDebugLog) {
+// Log.d(TAG, "Location: " + city);
+// }
+
+ // Detail fields
+ JSONObject details = dest.getJSONObject(FIELD_DETAILS_DETAILS);
+ long timezone = details.isNull(FIELD_DETAILS_TIMEZONE) ? 0L
+ : details.getLong(FIELD_DETAILS_TIMEZONE);
+ long altitude = details.getLong(FIELD_DETAILS_ALTITUDE);
+ String photos = details.has(FIELD_DETAILS_PHOTOS) ? details
+ .getString(FIELD_DETAILS_PHOTOS) : EMPTY_STRING;
+ String weather = details.has(FIELD_DETAILS_WEATHER) ? details
+ .getString(FIELD_DETAILS_WEATHER) : EMPTY_STRING;
+ String streetview = details.has(FIELD_DETAILS_STREETVIEW) ? details
+ .getString(FIELD_DETAILS_STREETVIEW) : EMPTY_STRING;
+ String gmmStreetview = details.has(FIELD_DETAILS_GMMSTREETVIEW) ? details
+ .getString(FIELD_DETAILS_GMMSTREETVIEW) : EMPTY_STRING;
+
+ try {
+ // All parsed, insert into DB
+ mDestinationDBHelper.insertDestination(db,
+ dest.getString(FIELD_IDENTIFIER),
+ dest.getLong(FIELD_ARRIVAL),
+ dest.getLong(FIELD_DEPARTURE),
+
+ city, region, country,
+
+ location.getDouble(FIELD_DETAILS_LOCATION_LAT),
+ location.getDouble(FIELD_DETAILS_LOCATION_LNG),
+ presentsTotal, presents, timezone, altitude, photos, weather,
+ streetview, gmmStreetview);
+ } catch (android.database.sqlite.SQLiteConstraintException e) {
+ // ignore duplicate locations
+ }
+ }
+
+ db.setTransactionSuccessful();
+ // Update mPreferences
+ mPreferences.setDBTimestamp(System.currentTimeMillis());
+ mPreferences.setTotalPresents(previousPresents);
+ return i;
+ } catch (JSONException e) {
+ Log.d(TAG, "Santa location tracking error 30");
+ SantaLog.d(TAG, "JSON Exception", e);
+ } finally {
+ db.endTransaction();
+ }
+
+ return 0;
+ }
+
+ private int processStream(JSONArray json, boolean isWear) {
+ SQLiteDatabase db = mStreamDBHelper.getWritableDatabase();
+
+ db.beginTransaction();
+ try {
+ // loop over each card
+
+ int i;
+ for (i = 0; i < json.length(); i++) {
+ JSONObject card = json.getJSONObject(i);
+
+ final long timestamp = card
+ .getLong(FIELD_STREAM_TIMESTAMP);
+ final String status = getExistingJSONString(card, FIELD_STREAM_STATUS);
+ final String didYouKnow = getExistingJSONString(card, FIELD_STREAM_DIDYOUKNOW);
+ final String imageUrl = getExistingJSONString(card, FIELD_STREAM_IMAGEURL);
+ final String youtubeId = getExistingJSONString(card, FIELD_STREAM_YOUTUBEID);
+
+// if (mDebugLog) {
+// Log.d(TAG, "Notification: " + timestamp);
+// }
+
+ try {
+ // All parsed, insert into DB
+ mStreamDBHelper.insert(db, timestamp, status, didYouKnow, imageUrl, youtubeId,
+ isWear);
+ } catch (android.database.sqlite.SQLiteConstraintException e) {
+ // ignore duplicate cards
+ }
+ }
+
+ db.setTransactionSuccessful();
+ return i;
+ } catch (JSONException e) {
+ Log.d(TAG, "Santa location tracking error 31");
+ SantaLog.d(TAG, "JSON Exception", e);
+ } finally {
+ db.endTransaction();
+ }
+
+ return 0;
+ }
+
+ // Reads an InputStream and converts it to a String.
+ protected static StringBuilder read(InputStream stream) throws IOException {
+ BufferedReader reader;
+ StringBuilder builder = new StringBuilder();
+ reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
+
+ for (String line; (line = reader.readLine()) != null; ) {
+ builder.append(line);
+ }
+
+ return builder;
+ }
+
+ /**
+ * Returns the value of the JSON field identified by the name. Returns null if the field does
+ * not exist.
+ */
+ public static String getExistingJSONString(JSONObject json, String name) throws JSONException {
+ if (json.has(name)) {
+ return json.getString(name);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the double of the JSON object identified by the name. Returns {@link
+ * java.lang.Double#MAX_VALUE} if the field does not exist.
+ */
+ public static double getExistingJSONDouble(JSONObject json, String name) throws JSONException {
+ if (json.has(name)) {
+ return json.getDouble(name);
+ } else {
+ return Double.MAX_VALUE;
+ }
+ }
+
+ interface APICallback {
+
+ void onNewSwitchOffState(boolean isOn);
+
+ /**
+ * Called when a new fingerprint has been detected and stored data
+ * will be cleared to process the new route.
+ *
+ * @see #onNewRouteLoaded()
+ */
+ void onNewFingerprint();
+
+ void onNewOffset();
+
+ /**
+ * Called when new data has been processed.
+ */
+ void onNewRouteLoaded();
+
+ void onNewStreamLoaded();
+
+ void onNewNotificationStreamLoaded();
+
+ void notifyRouteUpdating();
+
+ void onNewCastState(boolean disableCast);
+
+ void onNewGameState(boolean disableGumball, boolean disableJetpack,
+ boolean disableMemory, boolean disableRocket,
+ boolean disableDancer, boolean disableSnowdown);
+
+ void onNewVideos(String video1, String video15, String video23);
+
+ void onNewDestinationPhotoState(boolean disableDestinationPhoto);
+
+ void onNewApiDataAvailable();
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/LocalApiProcessor.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/LocalApiProcessor.java
new file mode 100644
index 000000000..0fb07a014
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/LocalApiProcessor.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.os.Environment;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.data.StreamDbHelper;
+import com.google.android.apps.santatracker.data.Switches;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * ApiProcessor that loads data from a local file (/sdcard/santa.json).
+ *
+ * If the now
parameter in the file is negative, the current system time is used
+ * instead.
+ *
+ * (The application expects correct behavior, ie. the API will return following locations if
+ * the fingerprint is unchanged.
+ */
+public class LocalApiProcessor extends APIProcessor {
+
+ private static final String FILENAME = "santa.json";
+ private static final String TAG = "SantaLocalApiProcessor";
+ protected final static String FIELD_CLIENT_SPECIFIC = "clientSpecific";
+
+ private File mFile;
+ private JSONObject mClientConfig;
+
+ public LocalApiProcessor(SantaPreferences mPreferences, DestinationDbHelper mDBHelper,
+ StreamDbHelper streamDBHelper, APICallback mCallback) {
+ super(mPreferences, mDBHelper, streamDBHelper, mCallback);
+ mFile = new File(Environment.getExternalStorageDirectory(), FILENAME);
+ }
+
+ @Override
+ public JSONObject loadApi(String url) {
+
+ SantaLog.d(TAG, "Loading local data.");
+ // read the santa json file from the SD card
+ String data = null;
+ try {
+ data = loadLocalFile();
+ } catch (IOException e) {
+ Log.d(TAG, "Communication Error 101");
+ return null;
+ }
+
+ if (data == null) {
+ Log.d(TAG, "Communication Error 102");
+ return null;
+ }
+
+ SantaLog.d(TAG, "Local File accessed, old data removed.");
+
+ // Parse data as JSON
+ try {
+ JSONObject result = parseData(data);
+ mClientConfig = result.getJSONObject(FIELD_CLIENT_SPECIFIC);
+ return result;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Log.d(TAG, "Communication Error 103");
+ }
+
+ return null;
+ }
+
+ @Override
+ protected Switches getSwitches() {
+ if (mClientConfig == null) {
+ throw new IllegalStateException("Can't call getSwitches() before successful loadApi()");
+ }
+
+ try {
+ Switches config = new Switches();
+
+ config.disableCastButton = mClientConfig.getBoolean(FIELD_DISABLE_CASTBUTTON);
+ config.disableDestinationPhoto = mClientConfig.getBoolean(FIELD_DISABLE_PHOTO);
+ config.disableGumballGame = mClientConfig.getBoolean(FIELD_DISABLE_GUMBALLGAME);
+ config.disableJetpackGame = mClientConfig.getBoolean(FIELD_DISABLE_JETPACKGAME);
+ config.disableMemoryGame = mClientConfig.getBoolean(FIELD_DISABLE_MEMORYGAME);
+ config.disableRocketGame = mClientConfig.getBoolean(FIELD_DISABLE_ROCKETGAME);
+ config.disableDancerGame = mClientConfig.getBoolean(FIELD_DISABLE_DANCERGAME);
+ config.disableSnowdownGame = mClientConfig.getBoolean(FIELD_DISABLE_SNOWDOWNGAME);
+
+ config.video1 = mClientConfig.getString(FIELD_VIDEO_1);
+ config.video15 = mClientConfig.getString(FIELD_VIDEO_15);
+ config.video23 = mClientConfig.getString(FIELD_VIDEO_23);
+
+ return config;
+ } catch (JSONException e) {
+ Log.d(TAG, "Communication Error 104");
+ return null;
+ }
+ }
+
+ private JSONObject parseData(String data) throws JSONException {
+ JSONObject json = new JSONObject(data);
+
+ // Replace the offset with a hardcoded offset
+
+ // Set the current time to now if the now timestamp is negative
+ if (json.getLong(FIELD_NOW) < 0L) {
+ json.put(FIELD_NOW, System.currentTimeMillis());
+ }
+
+ // Reset the fingerprint with each request for testing
+ // json.put(FIELD_FINGERPRINT, Long.toString(System.currentTimeMillis()));
+
+ return json;
+ }
+
+ private String loadLocalFile() throws IOException {
+ if (!mFile.isFile() || !mFile.canRead()) {
+ // Could not open file for reading
+ return null;
+ }
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(mFile);
+ return read(fis).toString();
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ }
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/RemoteApiProcessor.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/RemoteApiProcessor.java
new file mode 100644
index 000000000..88d0e1a0d
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/RemoteApiProcessor.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.google.android.apps.santatracker.BuildConfig;
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.data.StreamDbHelper;
+import com.google.android.apps.santatracker.data.Switches;
+import com.google.android.apps.santatracker.util.SantaLog;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
+import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
+import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * ApiProcessor that loads data from the remote Santa API.
+ */
+public class RemoteApiProcessor extends APIProcessor {
+
+ private static final String TAG = "RemoteApiProcessor";
+
+ private static final String SUPPORTED_SANTA_HEADER_API = "2";
+ private static final String API_VERSION_FIELD = "X-Santa-Version";
+ private static final long CACHE_EXPIRY_S = 60 * 12; // This gets us 5 requests / hr
+
+ private final FirebaseRemoteConfig mConfig = FirebaseRemoteConfig.getInstance();
+
+ public RemoteApiProcessor(SantaPreferences mPreferences, DestinationDbHelper mDBHelper,
+ StreamDbHelper streamDbHelper, APICallback callback) {
+ super(mPreferences, mDBHelper, streamDbHelper, callback);
+ initializeRemoteConfigApi(callback);
+ }
+
+ @Override
+ public JSONObject loadApi(String url) {
+ // Retrieve and parse the json data.
+ String data;
+
+ SantaLog.d(TAG, "Accessing API: "+url);
+
+ try {
+ data = downloadUrl(url);
+
+ } catch (IOException e1) {
+ Log.d(TAG, "Santa Communication Error 0");
+ return null;
+ }
+
+ // Check that data was retrieved
+ if (data == null) {
+ Log.d(TAG, "Santa Communication Error 1");
+ return null;
+ }
+
+ // parse data as json
+ try {
+ return new JSONObject(data);
+ } catch (JSONException e) {
+ Log.d(TAG, "Santa Communication Error 2");
+ }
+ return null;
+ }
+
+ private void initializeRemoteConfigApi(final APICallback callback) {
+ FirebaseRemoteConfigSettings settings = new FirebaseRemoteConfigSettings.Builder()
+ .setDeveloperModeEnabled(BuildConfig.DEBUG)
+ .build();
+
+ mConfig.setConfigSettings(settings);
+ mConfig.setDefaults(R.xml.remote_config_defaults);
+ Log.i(TAG, "Fetching Santa config");
+ mConfig.fetch(CACHE_EXPIRY_S).addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ Log.i(TAG, "Santa config result: Success");
+ mConfig.activateFetched();
+ callback.onNewApiDataAvailable();
+ } else {
+ Log.w(TAG, "Santa config result: Failed", task.getException());
+ }
+ }
+ });
+ }
+
+ @Override
+ protected Switches getSwitches() {
+ Switches switches = new Switches();
+
+ switches.disableCastButton = mConfig.getBoolean(FIELD_DISABLE_CASTBUTTON);
+ switches.disableDestinationPhoto = mConfig.getBoolean(FIELD_DISABLE_PHOTO);
+ switches.disableGumballGame = mConfig.getBoolean(FIELD_DISABLE_GUMBALLGAME);
+ switches.disableJetpackGame = mConfig.getBoolean(FIELD_DISABLE_JETPACKGAME);
+ switches.disableMemoryGame = mConfig.getBoolean(FIELD_DISABLE_MEMORYGAME);
+ switches.disableRocketGame = mConfig.getBoolean(FIELD_DISABLE_ROCKETGAME);
+ switches.disableDancerGame = mConfig.getBoolean(FIELD_DISABLE_DANCERGAME);
+ switches.disableSnowdownGame = mConfig.getBoolean(FIELD_DISABLE_SNOWDOWNGAME);
+
+ switches.video1 = mConfig.getString(FIELD_VIDEO_1);
+ switches.video15 = mConfig.getString(FIELD_VIDEO_15);
+ switches.video23 = mConfig.getString(FIELD_VIDEO_23);
+
+ return switches;
+ }
+
+ /**
+ * Downloads the given URL and return
+ */
+ protected String downloadUrl(String myurl) throws IOException {
+ InputStream is = null;
+ // Only display the first 500 characters of the retrieved
+ // web page content.
+ // int len = 500;
+
+ try {
+ URL url = new URL(myurl);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setReadTimeout(10000);
+ conn.setConnectTimeout(15000);
+ conn.setRequestMethod("GET");
+ conn.setDoInput(true);
+ // Starts the query
+ conn.connect();
+ int response = conn.getResponseCode();
+ if (!isValidHeader(conn)) {
+ // not a valid header
+ Log.d(TAG, "Santa communication failure.");
+ return null;
+
+ } else if (response != 200) {
+ Log.d(TAG, "Santa communication failure " + response);
+ return null;
+
+ } else {
+ is = conn.getInputStream();
+
+ // Convert the InputStream into a string
+ return read(is).toString();
+ }
+
+ // Makes sure that the InputStream is closed
+ } finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+
+ /**
+ * Returns true if this application can handle requests of this version,
+ * false otherwise. The current API version can be retrieved through:
+ * curl -sI 'http://santa-api.appspot.com/info' | grep X-Santa
+ */
+ protected boolean isValidHeader(HttpURLConnection connection) {
+
+ String version = connection.getHeaderField(API_VERSION_FIELD);
+ // if the version matches supported version, returns true, false if no
+ // header is set or it is not recognised.
+ return version != null && version.equals(SUPPORTED_SANTA_HEADER_API);
+
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaService.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaService.java
new file mode 100644
index 000000000..3ee4961a4
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaService.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.data.StreamDbHelper;
+import com.google.android.apps.santatracker.util.SantaLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TimeZone;
+
+
+public class SantaService extends Service implements APIProcessor.APICallback {
+
+ private static final String TAG = "SantaCommunicator";
+
+ private static final String FILENAME_OVERRIDE = "santa_config.txt";
+
+ // Parameters from config resources
+ private String API_URL;
+ private String API_CLIENT;
+ private String LANGUAGE;
+ public int INITIAL_BACKOFF_TIME;
+ public int MAX_BACKOFF_TIME;
+ public float BACKOFF_FACTOR;
+ private static final int TIMEZONE = TimeZone.getDefault().getRawOffset();
+
+ private long mBackoff;
+
+ private SantaPreferences mPreferences;
+ private DestinationDbHelper mDbHelper;
+ private StreamDbHelper mStreamDbHelper;
+
+ // current state of the service
+ private int mState = SantaServiceMessages.STATUS_IDLE_NODATA;
+
+ private ArrayList mClients = new ArrayList<>(2);
+ private final ArrayList mPendingClients = new ArrayList<>(2);
+
+ private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
+
+ private APIProcessor mApiProcessor;
+
+ private Handler mHandler = null;
+ private HandlerThread mApiThread = new HandlerThread("ApiThread");
+
+ private Runnable mApiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Prevent clients from being added during thread execution
+ synchronized (mPendingClients) {
+
+ // Sanity check to ensure that next access timestamp has been met
+ if (System.currentTimeMillis() < mPreferences.getNextInfoAPIAccess()) {
+ SantaLog.d(TAG, "Did not run API thread, next API access not expired: next="
+ + mPreferences.getNextInfoAPIAccess() + " ,current=" + System
+ .currentTimeMillis() +
+ ", diff=" + (mPreferences.getNextInfoAPIAccess() - System
+ .currentTimeMillis()));
+ //reschedule
+ scheduleApiAccess();
+
+ // skip if no clients are registered
+ } else if (!mClients.isEmpty() || !mPendingClients.isEmpty()) {
+ sendPendingState();
+ long delay = accessInfoAPI();
+ sendPendingState();
+ mPreferences.setNextInfoAPIAccess(delay + System.currentTimeMillis());
+ SantaLog.d(TAG, "delay=" + delay + ", next access=" + mPreferences
+ .getNextInfoAPIAccess() + " current time=" + System.currentTimeMillis()
+ + " diff=" + (mPreferences.getNextInfoAPIAccess() - System
+ .currentTimeMillis()));
+
+ // do not reschedule unless there are clients registered
+ if (!mClients.isEmpty() || !mPendingClients.isEmpty()) {
+ scheduleApiAccess();
+ } else {
+ SantaLog.d(TAG, "No clients registered, access not scheduled.");
+ }
+ }
+ }
+
+ }
+ };
+
+
+ private void scheduleApiAccess() {
+ mHandler.removeCallbacksAndMessages(null);
+
+ final long nextAccess = mPreferences.getNextInfoAPIAccess() - System.currentTimeMillis();
+ if (nextAccess <= 0) {
+ SantaLog.d(TAG, "schedule: negative, post now.");
+ // run straight away
+ mHandler.post(mApiRunnable);
+ } else {
+ SantaLog.d(TAG, "schedule: positive, postDelayed in: " + nextAccess);
+ mHandler.postDelayed(mApiRunnable, nextAccess);
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mState = SantaServiceMessages.STATUS_IDLE_NODATA;
+ mApiThread.start();
+ mHandler = new Handler(mApiThread.getLooper());
+
+ // initialise config values
+ final Resources res = getResources();
+ INITIAL_BACKOFF_TIME = res.getInteger(R.integer.backoff_initital);
+ MAX_BACKOFF_TIME = res.getInteger(R.integer.backoff_max);
+ BACKOFF_FACTOR = ((float) res.getInteger(R.integer.backoff_factor)) / 100f;
+ mBackoff = INITIAL_BACKOFF_TIME;
+
+ LANGUAGE = Locale.getDefault().getLanguage();
+
+ mPreferences = new SantaPreferences(getApplicationContext());
+ mDbHelper = DestinationDbHelper.getInstance(getApplicationContext());
+ mStreamDbHelper = StreamDbHelper.getInstance(getApplicationContext());
+
+ // invalidate all data if database has been upgraded (or started for the
+ // first time)
+ if (mPreferences.getDestDBVersion() != DestinationDbHelper.DATABASE_VERSION ||
+ mPreferences.getStreamDBVersion() != StreamDbHelper.DATABASE_VERSION) {
+ SantaLog.d(TAG, "Data is invalid - reinitialising.");
+ mDbHelper.reinitialise();
+ mStreamDbHelper.reinitialise();
+ mPreferences.invalidateData();
+ mPreferences.setDestDBVersion(DestinationDbHelper.DATABASE_VERSION);
+ mPreferences.setStreamDBVersion(StreamDbHelper.DATABASE_VERSION);
+ }
+
+ // ensure a valid rand value is stored
+ if (mPreferences.getRandValue() < 0) {
+ // invalid rand value, generate new value and update preference
+ float rand = (float) Math.random();
+ mPreferences.setRandValue(rand);
+ }
+
+ // Read in the URL and CLIENT values from the sdcard if the file exists, otherwise use
+ // defaults from resources
+ if (!setOverrideConfigValues()) {
+ API_URL = res.getString(R.string.api_url);
+ API_CLIENT = res.getString(R.string.config_api_client);
+ }
+
+ // Initialise the ApiProcessor. If the client is "local" use the special debug processor for
+ // a local file.
+ if (API_CLIENT.equals("local")) {
+ Toast.makeText(this, "Using Local API file!", Toast.LENGTH_SHORT).show();
+ // For a local data file, remove all existing data first when the file is initialised
+ mApiProcessor = new LocalApiProcessor(mPreferences, mDbHelper, mStreamDbHelper, this);
+ } else {
+ // Default processor that accesses the remote api via HTTPS.
+ mApiProcessor = new RemoteApiProcessor(mPreferences, mDbHelper, mStreamDbHelper, this);
+ }
+
+ // Check state of data - is it up to date?
+ if (haveValidData()) {
+ mState = SantaServiceMessages.STATUS_IDLE;
+ } else {
+ mState = SantaServiceMessages.STATUS_IDLE_NODATA;
+ }
+
+ }
+
+ /**
+ * Attempt to read in a file from external storage with config options.
+ * The file needs to be located in {@link android.os.Environment#getExternalStorageDirectory()}
+ * and named {@link #FILENAME_OVERRIDE}. It contains one line of text: the client name,
+ * followed
+ * by the API URL.
+ */
+ private boolean setOverrideConfigValues() {
+
+ File f = new File(Environment.getExternalStorageDirectory(), FILENAME_OVERRIDE);
+
+ if (f.exists()) {
+ try {
+ BufferedReader br = new BufferedReader(new FileReader(f));
+ String line = br.readLine();
+ br.close();
+
+ // parse the line
+ final int commaPosition = line.indexOf(',');
+ if (commaPosition > 0) {
+ final String client = line.substring(0, commaPosition);
+ final String url = line.substring(commaPosition + 1);
+ if (!(client.length() == 0) && !(url.length() == 0)) {
+ Log.d(TAG, "Config Override: client=" + client + " , url=" + url);
+ API_URL = url;
+ API_CLIENT = client;
+ Toast.makeText(this, "API Client Override: " + API_CLIENT,
+ Toast.LENGTH_LONG)
+ .show();
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ return false;
+ }
+
+ private boolean haveValidData() {
+ // Need valid preference data and more destinations
+ return mPreferences.hasValidData() &&
+ mDbHelper.getLastDeparture() > SantaPreferences.getCurrentTime();
+ }
+
+ /**
+ * Access the INFO API, returns the delay in ms when the API should be accessed again
+ */
+ private long accessInfoAPI() {
+ // Access the Info API
+ mState = SantaServiceMessages.STATUS_PROCESSING;
+
+ // Construct URL
+ final String url = String.format(Locale.US, API_URL, API_CLIENT,
+ mPreferences.getRandValue(), mPreferences.getRouteOffset(), mPreferences.getStreamOffset(), TIMEZONE, LANGUAGE,
+ mPreferences.getFingerprint());
+
+ Log.d(TAG, "Tracking Santa.");
+ long result = mApiProcessor.accessAPI(url);
+ if (result < 0) {
+ // API access was unsuccessful, back-off and try again later
+ // Calculate delay, up to the max backoff time
+ long delay = (long) Math.min((mBackoff * BACKOFF_FACTOR), MAX_BACKOFF_TIME);
+ Log.d(TAG, "Couldn't communicate with Santa, trying again in: " + delay);
+ mBackoff = delay;
+
+ // Notify clients that there was an error and set state
+ if (haveValidData()) {
+ mState = SantaServiceMessages.STATUS_ERROR;
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_ERROR));
+ } else {
+ mState = SantaServiceMessages.STATUS_ERROR_NODATA;
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_ERROR_NODATA));
+ }
+ return delay;
+
+ } else {
+ SantaLog.d(TAG, "Accessed API, next access in: " + result);
+ // reset back-off time
+ mBackoff = INITIAL_BACKOFF_TIME;
+
+ // Notify clients that API access was successful
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_SUCCESS));
+ mState = SantaServiceMessages.STATUS_IDLE;
+
+ return result;
+ }
+
+ }
+
+ @Override
+ public void onNewSwitchOffState(boolean isOff) {
+ sendMessage(SantaServiceMessages.getSwitchOffMessage(isOff));
+ }
+
+ @Override
+ public void onNewFingerprint() {
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_UPDATED_FINGERPRINT));
+ }
+
+ @Override
+ public void onNewOffset() {
+ sendMessage(getTimeUpdateMessage());
+ }
+
+ @Override
+ public void onNewRouteLoaded() {
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_UPDATED_ROUTE));
+
+ // Send a time update message, to ensure we don't leave the client in a state where it
+ // thinks it has a route but no timestamp information.
+ onNewOffset();
+ }
+
+ @Override
+ public void onNewStreamLoaded() {
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_UPDATED_STREAM));
+ }
+
+ @Override
+ public void onNewNotificationStreamLoaded() {
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_UPDATED_WEARSTREAM));
+ }
+
+ @Override
+ public void notifyRouteUpdating() {
+ sendMessage(Message.obtain(null, SantaServiceMessages.MSG_INPROGRESS_UPDATE_ROUTE));
+ }
+
+ @Override
+ public void onNewCastState(boolean isDisabled) {
+ sendMessage(SantaServiceMessages.getCastDisabledMessage(isDisabled));
+ }
+
+ @Override
+ public void onNewGameState(boolean disableGumball, boolean disableJetpack,
+ boolean disableMemory, boolean disableRocket,
+ boolean disableDancer, boolean disableSnowdown) {
+ sendMessage(SantaServiceMessages.getGamesMessage(disableGumball, disableJetpack,
+ disableMemory, disableRocket, disableDancer, disableSnowdown));
+ }
+
+ @Override
+ public void onNewVideos(String video1, String video15, String video23) {
+ sendMessage(SantaServiceMessages.getVideosMessage(video1, video15, video23));
+ }
+
+ @Override
+ public void onNewDestinationPhotoState(boolean isDisabled) {
+ sendMessage(SantaServiceMessages.getDestinationPhotoMessage(isDisabled));
+ }
+
+ @Override
+ public void onNewApiDataAvailable() {
+ // Force an API update immediately.
+ mPreferences.setNextInfoAPIAccess(-1);
+ scheduleApiAccess();
+ }
+
+ private void sendMessage(Message msg) {
+ for (int i = 0; i < mClients.size(); i++) {
+ try {
+ // TODO - Message below is duplicated to avoid
+ // IllegalStateException regarding queued messages.
+ // Is there a cleaner way to do this or avoid altogether?
+ Message target = new Message();
+ target.copyFrom(msg);
+ mClients.get(i).send(target);
+ } catch (RemoteException e) {
+ // Could not communicate with client, remove
+ mClients.remove(i);
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (mHandler == null || !mApiThread.isAlive()) {
+ mApiThread.start();
+ mHandler = new Handler(mApiThread.getLooper());
+ }
+
+ scheduleApiAccess();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mHandler == null || !mApiThread.isAlive()) {
+ mApiThread.start();
+ mHandler = new Handler(mApiThread.getLooper());
+ }
+
+ scheduleApiAccess();
+ return mIncomingMessenger.getBinder();
+
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (mPendingClients.isEmpty() && mClients.isEmpty()) {
+ // No clients connected, remove scheduled API execution
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ SantaLog.d(TAG, "last client unbind, removed scheduled threads");
+ }
+ }
+ return super.onUnbind(intent);
+ }
+
+ private Message getTimeUpdateMessage() {
+ final long offset = mPreferences.getOffset();
+ final long firstDeparture = mDbHelper.getFirstDeparture();
+ final long finalArrival = mDbHelper.getLastArrival();
+ final long finalDeparture = mDbHelper.getLastDeparture();
+ return SantaServiceMessages.getTimeUpdateMessage(
+ offset, firstDeparture, finalArrival, finalDeparture);
+ }
+
+ /**
+ * Send the current state of the application to all pending clients.
+ */
+ private synchronized void sendPendingState() {
+
+ if (!mPendingClients.isEmpty()) {
+ final Message[] messages = new Message[]{
+ SantaServiceMessages.getBeginFullStateMessage(),
+ SantaServiceMessages.getSwitchOffMessage(mPreferences.getSwitchOff()),
+ getTimeUpdateMessage(),
+ SantaServiceMessages.getCastDisabledMessage(mPreferences.getCastDisabled()),
+ SantaServiceMessages.getGamesMessage(mPreferences.getGumballDisabled(),
+ mPreferences.getJetpackDisabled(), mPreferences.getMemoryDisabled(),
+ mPreferences.getRocketDisabled(), mPreferences.getDancerDisabled(),
+ mPreferences.getSnowdownDisabled()),
+ SantaServiceMessages
+ .getDestinationPhotoMessage(mPreferences.getDestinationPhotoDisabled()),
+ SantaServiceMessages.getStateMessage(mState),
+ SantaServiceMessages.getVideosMessage(mPreferences.getVideos())
+ };
+
+ for (int i = 0; i < mPendingClients.size(); i++) {
+ final Messenger messenger = mPendingClients.get(i);
+
+ try {
+ for (Message msg : messages) {
+ messenger.send(msg);
+ }
+ // mark client as active
+ mClients.add(messenger);
+ } catch (RemoteException e) {
+ // client is dead, ignore client
+ }
+ mPendingClients.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Handler for communication from a client to this Service. Registers and unregisters clients.
+ */
+ class IncomingHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SantaServiceMessages.MSG_SERVICE_REGISTER_CLIENT:
+ Messenger m = msg.replyTo;
+ mPendingClients.add(m);
+
+ if (mState != SantaServiceMessages.STATUS_PROCESSING) {
+ // send data if the background process is not currently running
+ synchronized (mPendingClients) {
+ sendPendingState();
+ }
+ scheduleApiAccess();
+ } else {
+ // Other state, notify client right away
+ try {
+ m.send(SantaServiceMessages.getStateMessage(mState));
+ } catch (RemoteException e) {
+ // Could not contact client, remove from pending list
+ mPendingClients.remove(m);
+ }
+ }
+ break;
+ case SantaServiceMessages.MSG_SERVICE_UNREGISTER_CLIENT:
+ // Attempt to remove client from active list, alternatively from pending list
+ if (!mClients.remove(msg.replyTo)) {
+ mPendingClients.remove(msg.replyTo);
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaServiceMessages.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaServiceMessages.java
new file mode 100644
index 000000000..2b8bbb9e7
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/service/SantaServiceMessages.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 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.service;
+
+import android.os.Bundle;
+import android.os.Message;
+
+public abstract class SantaServiceMessages {
+
+ // IPC Messenger communication messages
+ public static final int MSG_SERVICE_REGISTER_CLIENT = 1001;
+ public static final int MSG_SERVICE_UNREGISTER_CLIENT = 1002;
+
+ public static final int MSG_SERVICE_STATE_BEGIN = 9;
+ public static final int MSG_SERVICE_STATUS = 10;
+
+ public static final int MSG_INPROGRESS_UPDATE_ROUTE = 11;
+
+ public static final int MSG_UPDATED_ROUTE = 20;
+ public static final int MSG_UPDATED_ONOFF = 21;
+ public static final int MSG_UPDATED_TIMES = 22;
+ public static final int MSG_UPDATED_FINGERPRINT = 23;
+ public static final int MSG_UPDATED_CASTDISABLED = 24;
+ public static final int MSG_UPDATED_GAMES = 25;
+ public static final int MSG_UPDATED_VIDEOS = 26;
+ public static final int MSG_UPDATED_STREAM = 27;
+ public static final int MSG_UPDATED_WEARSTREAM = 28;
+ public static final int MSG_UPDATED_DESTINATIONPHOTO = 29;
+
+ public static final int MSG_ERROR = 98;
+ public static final int MSG_ERROR_NODATA = 99;
+ public static final int MSG_SUCCESS = 100;
+
+ public static final int MSG_FLAG_GAME_GUMBALL = 1;
+ public static final int MSG_FLAG_GAME_JETPACK = 2;
+ public static final int MSG_FLAG_GAME_MEMORY = 4;
+ public static final int MSG_FLAG_GAME_ROCKET = 8;
+ public static final int MSG_FLAG_GAME_DANCER = 16;
+ public static final int MSG_FLAG_GAME_SNOWDOWN = 32;
+
+ // Service status state
+ public static final int STATUS_IDLE = 1;
+ public static final int STATUS_IDLE_NODATA = 2;
+ public static final int STATUS_PROCESSING = 3;
+ public static final int STATUS_ERROR = 4;
+
+ public static final int STATUS_ERROR_NODATA = 5;
+
+ // Flags
+ public static final int ENABLED = 1;
+ public static final int DISABLED = 2;
+ // Switchoff update
+ public static final int SWITCH_ON = 1;
+ public static final int SWITCH_OFF = 2;
+
+ // Bundle keys for time/offset update
+ public static final String BUNDLE_OFFSET = "OFFSET";
+ public static final String BUNDLE_FIRST_DEPARTURE = "FIRST_DEPARTURE";
+ public static final String BUNDLE_FINAL_ARRIVAL = "FINAL_ARRIVAL";
+ public static final String BUNDLE_FINAL_DEPARTURE = "FINAL_DEPARTURE";
+
+ // Bundle keys for video data
+ public static final String BUNDLE_VIDEOS = "VIDEOS";
+
+ private static Bundle timeBundle = new Bundle();
+
+ public static Message getSwitchOffMessage(boolean isOff) {
+ int status = isOff ? SantaServiceMessages.SWITCH_OFF : SantaServiceMessages.SWITCH_ON;
+ return Message.obtain(null, SantaServiceMessages.MSG_UPDATED_ONOFF, status, 0);
+ }
+
+ public static Message getBeginFullStateMessage() {
+ return Message.obtain(null, MSG_SERVICE_STATE_BEGIN);
+ }
+
+ public static Message getStateMessage(int state) {
+ return Message.obtain(null, SantaServiceMessages.MSG_SERVICE_STATUS, state, 0);
+ }
+
+ public static Message getTimeUpdateMessage(long offset, long firstDeparture,
+ long finalArrival, long finalDeparture) {
+ timeBundle.clear();
+ timeBundle.putLong(SantaServiceMessages.BUNDLE_OFFSET, offset);
+ timeBundle.putLong(SantaServiceMessages.BUNDLE_FIRST_DEPARTURE, firstDeparture);
+ timeBundle.putLong(SantaServiceMessages.BUNDLE_FINAL_ARRIVAL, finalArrival);
+ timeBundle.putLong(SantaServiceMessages.BUNDLE_FINAL_DEPARTURE, finalDeparture);
+
+ return Message.obtain(null, SantaServiceMessages.MSG_UPDATED_TIMES, timeBundle);
+ }
+
+ public static int getDisabledStatus(boolean isDisabled){
+ return isDisabled ? SantaServiceMessages.DISABLED : SantaServiceMessages.ENABLED;
+ }
+
+ public static Message getCastDisabledMessage(boolean isDisabled) {
+ return Message.obtain(null, SantaServiceMessages.MSG_UPDATED_CASTDISABLED,
+ getDisabledStatus(isDisabled), 0);
+ }
+
+ public static Message getDestinationPhotoMessage(boolean isDisabled) {
+ return Message.obtain(null, SantaServiceMessages.MSG_UPDATED_DESTINATIONPHOTO,
+ getDisabledStatus(isDisabled), 0);
+ }
+
+ public static Message getGamesMessage(boolean disableGumball, boolean disableJetpack,
+ boolean disableMemory, boolean disableRocket,
+ boolean disableDancer, boolean disableSnowdown) {
+ int status = 0;
+ status += disableGumball ? MSG_FLAG_GAME_GUMBALL : 0;
+ status += disableJetpack ? MSG_FLAG_GAME_JETPACK : 0;
+ status += disableMemory ? MSG_FLAG_GAME_MEMORY : 0;
+ status += disableRocket ? MSG_FLAG_GAME_ROCKET : 0;
+ status += disableDancer ? MSG_FLAG_GAME_DANCER : 0;
+ status += disableSnowdown ? MSG_FLAG_GAME_SNOWDOWN : 0;
+ return Message.obtain(null, SantaServiceMessages.MSG_UPDATED_GAMES, status, 0);
+ }
+
+ public static Message getVideosMessage(String... videos) {
+ Message message = Message.obtain(null, SantaServiceMessages.MSG_UPDATED_VIDEOS, 0, 0);
+ Bundle bundle = new Bundle();
+ bundle.putStringArray(BUNDLE_VIDEOS, videos);
+ message.setData(bundle);
+ return message;
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/AccessibilityUtil.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/AccessibilityUtil.java
new file mode 100644
index 000000000..82a77e333
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/AccessibilityUtil.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+
+import android.content.Context;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+/**
+ * Utility methods for accessibility support.
+ */
+public abstract class AccessibilityUtil {
+
+ /**
+ * Return true if the accessibility service or touch exploration are enabled.
+ */
+ public static boolean isTouchAccessiblityEnabled(Context context) {
+ AccessibilityManager am = (AccessibilityManager) context
+ .getSystemService(Context.ACCESSIBILITY_SERVICE);
+ boolean isAccessibilityEnabled = am.isEnabled();
+ boolean isTouchExplorationEnabled = am.isTouchExplorationEnabled();
+ return isAccessibilityEnabled || isTouchExplorationEnabled;
+ }
+
+ /**
+ * Announce text through the AccessibilityManager for a view.
+ *
+ * @param text
+ * @param view
+ * @param manager
+ */
+ public static void announceText(String text, View view, AccessibilityManager manager) {
+ // Only announce text if the accessibility service is enabled
+ if (!manager.isEnabled()) {
+ return;
+ }
+
+ AccessibilityEvent event = AccessibilityEvent
+ .obtain(AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ event.getText().add(text);
+ event.setEnabled(true);
+ // Tie the event to the view
+ event.setClassName(view.getClass().getName());
+ event.setPackageName(view.getContext().getPackageName());
+ AccessibilityEventCompat.asRecord(event).setSource(view);
+
+ // Send the announcement
+ manager.sendAccessibilityEvent(event);
+
+ }
+
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/Intents.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/Intents.java
new file mode 100644
index 000000000..24916f7c1
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/Intents.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import com.google.android.apps.santatracker.R;
+import com.google.android.apps.santatracker.data.Destination;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Utility methods for Intents.
+ */
+public class Intents {
+
+ private static final String GMM_PACKAGE = "com.google.android.apps.maps";
+ private static final String GMM_ACTIVITY = "com.google.android.maps.MapsActivity";
+
+ /**
+ * URL for YouTube video IDs.
+ */
+ private static final String VIDEO_URL = "https://www.youtube.com/watch?v=%s";
+
+ /**
+ * Constructs an Intent that plays back a YouTube video.
+ * If the YouTube app is installed, the video will be played back directly in full screen
+ * mode.
+ * if the YouTube app is not available (e.g. not installed or disabled), the video is launched
+ * in a browser instead.
+ *
+ * @param context
+ * @param videoId YouTube Video id.
+ * @return
+ */
+ public static Intent getYoutubeIntent(Context context, String videoId) {
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("vnd.youtube://" + videoId));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("force_fullscreen", true);
+
+ List resolvers = context.getPackageManager().queryIntentActivities(intent, 0);
+ if (resolvers != null && resolvers.size() > 0) {
+ // Devices with YouTube installed will get the native full-screen player
+ return intent;
+ } else {
+ // If YouTube is not available, load open the video in the browser
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(VIDEO_URL, videoId)));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+ }
+
+ /**
+ * Checks if the device can handle a GMM StreetView intent.
+ */
+ public static boolean canHandleStreetView(Context context) {
+ // Construct a fake streetView intent
+ Destination.StreetView sv = new Destination.StreetView();
+ Intent intent = getStreetViewIntent(context.getString(R.string.streetview_uri), sv);
+
+ List resolvers = context.getPackageManager().queryIntentActivities(intent, 0);
+ return resolvers != null && resolvers.size() > 0;
+ }
+
+ public static Intent getStreetViewIntent(String rawUri, Destination.StreetView streetView) {
+ Uri gmmIntentUri = Uri
+ .parse(String.format(Locale.US, rawUri, streetView.id, streetView.heading));
+ Intent intent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+ intent.setClassName(GMM_PACKAGE, GMM_ACTIVITY);
+
+ return intent;
+
+ }
+}
diff --git a/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/ScheduleNotificationService.java b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/ScheduleNotificationService.java
new file mode 100644
index 000000000..bf103132f
--- /dev/null
+++ b/santa-tracker/src/main/java/com/google/android/apps/santatracker/util/ScheduleNotificationService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import com.google.android.apps.santatracker.NotificationBroadcastReceiver;
+import com.google.android.apps.santatracker.common.NotificationConstants;
+import com.google.android.apps.santatracker.data.DestinationDbHelper;
+import com.google.android.apps.santatracker.data.SantaPreferences;
+import com.google.android.apps.santatracker.data.StreamCursor;
+import com.google.android.apps.santatracker.data.StreamDbHelper;
+import com.google.android.apps.santatracker.data.StreamEntry;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+
+/**
+ * Intentservice that schedules the next (wear) notification. Notifications are scheduled as
+ * PendingIntents that are received by the {@link com.google.android.apps.santatracker.NotificationBroadcastReceiver}.
+ * Scheduling these notifications requires database access, which is why the logic has been moved
+ * here.
+ */
+public class ScheduleNotificationService extends IntentService {
+
+ private static final String TAG = "ScheduleNotificationService";
+
+ public ScheduleNotificationService() {
+ super("ScheduleNotificationService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ scheduleNextNotification();
+ }
+
+ private void scheduleNextNotification() {
+ SantaLog.d(TAG, "Scheduling next Notification");
+ StreamDbHelper db = StreamDbHelper.getInstance(this);
+
+ // Get all following notifications
+ Cursor c = db.getFollowing(SantaPreferences.getCurrentTime(), true);
+ if (c.isAfterLast()) {
+ // No notifications left, cancel existing notification and do not schedule a new one
+ cancelPending();
+ return;
+ }
+ StreamCursor stream = new StreamCursor(c);
+ StreamEntry entry = stream.getCurrent();
+ c.close();
+
+ DestinationDbHelper destionationHelper = DestinationDbHelper.getInstance(this);
+ final long finalArrival = destionationHelper.getLastArrival();
+
+ // Schedule execution
+ Intent i = new Intent(getApplicationContext(), NotificationBroadcastReceiver.class);
+ i.putExtra(NotificationConstants.KEY_TIMESTAMP, entry.timestamp);
+ i.putExtra(NotificationConstants.KEY_FACT, entry.didYouKnow);
+ i.putExtra(NotificationConstants.KEY_IMAGEURL, entry.image);
+ i.putExtra(NotificationConstants.KEY_STATUS, entry.santaStatus);
+ i.putExtra(NotificationConstants.KEY_FINAL_ARRIVAL, finalArrival);
+
+ // Notification type
+ i.putExtra(NotificationConstants.KEY_NOTIFICATION_TYPE,
+ NotificationConstants.NOTIFICATION_FACT);
+
+ // Overwrite any already pending intents
+ PendingIntent pi = getPendingIntent(i);
+
+ final long time = SantaPreferences.getAdjustedTime(entry.timestamp);
+
+ // Only schedule a notification if the time is in the future, otherwise skip it
+ if(time < System.currentTimeMillis()){
+ return ;
+ }
+
+ // Deliver next time the device is woken up
+ AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ alarm.set(AlarmManager.RTC, time, pi);
+
+ SantaLog.d(TAG, "Scheduled notification: " + time + " ; in: " + (System.currentTimeMillis()
+ - time));
+ }
+
+ private PendingIntent getPendingIntent(Intent i) {
+ return PendingIntent
+ .getBroadcast(getApplicationContext(), NotificationConstants.NOTIFICATION_FACT, i,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ }
+
+ private void cancelPending() {
+ SantaLog.d(TAG, "Cancelled pending intent.");
+ // Need identical intent (sans extras) to cancel pending intent.
+ getPendingIntent(new Intent()).cancel();
+ }
+}
diff --git a/santa-tracker/src/main/lint.xml b/santa-tracker/src/main/lint.xml
new file mode 100644
index 000000000..a09dfc009
--- /dev/null
+++ b/santa-tracker/src/main/lint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/proguard-project.txt b/santa-tracker/src/main/proguard-project.txt
new file mode 100644
index 000000000..f2fe1559a
--- /dev/null
+++ b/santa-tracker/src/main/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/santa-tracker/src/main/res/anim/card_answer_flash.xml b/santa-tracker/src/main/res/anim/card_answer_flash.xml
new file mode 100644
index 000000000..0e6a53543
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/card_answer_flash.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/fade_in_dash.xml b/santa-tracker/src/main/res/anim/fade_in_dash.xml
new file mode 100644
index 000000000..05d676c8f
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/fade_in_dash.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/fade_in_topright.xml b/santa-tracker/src/main/res/anim/fade_in_topright.xml
new file mode 100644
index 000000000..f543b815d
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/fade_in_topright.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/fade_out_dash.xml b/santa-tracker/src/main/res/anim/fade_out_dash.xml
new file mode 100644
index 000000000..2d1a041f1
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/fade_out_dash.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/fade_out_topright.xml b/santa-tracker/src/main/res/anim/fade_out_topright.xml
new file mode 100644
index 000000000..c203cfb56
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/fade_out_topright.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/left_pane_slide_out.xml b/santa-tracker/src/main/res/anim/left_pane_slide_out.xml
new file mode 100644
index 000000000..a1790b1bf
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/left_pane_slide_out.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/level_fade_out_anim.xml b/santa-tracker/src/main/res/anim/level_fade_out_anim.xml
new file mode 100644
index 000000000..5b9f83e3b
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/level_fade_out_anim.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/play_again_bkgrd_anim.xml b/santa-tracker/src/main/res/anim/play_again_bkgrd_anim.xml
new file mode 100644
index 000000000..fbf591848
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/play_again_bkgrd_anim.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/anim/play_again_main_anim.xml b/santa-tracker/src/main/res/anim/play_again_main_anim.xml
new file mode 100644
index 000000000..c3d45bc73
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/play_again_main_anim.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/santa-tracker/src/main/res/anim/right_pane_slide_in.xml b/santa-tracker/src/main/res/anim/right_pane_slide_in.xml
new file mode 100644
index 000000000..c78a640d4
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/right_pane_slide_in.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/santa_wave.xml b/santa-tracker/src/main/res/anim/santa_wave.xml
new file mode 100644
index 000000000..37480b0f7
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/santa_wave.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/santa-tracker/src/main/res/anim/santacam_message.xml b/santa-tracker/src/main/res/anim/santacam_message.xml
new file mode 100644
index 000000000..27f3916fe
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/santacam_message.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/scale_level_anim_down.xml b/santa-tracker/src/main/res/anim/scale_level_anim_down.xml
new file mode 100644
index 000000000..33a4ee590
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/scale_level_anim_down.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/scale_up_level_anim.xml b/santa-tracker/src/main/res/anim/scale_up_level_anim.xml
new file mode 100644
index 000000000..ed3626986
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/scale_up_level_anim.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/slide_in_bottom.xml b/santa-tracker/src/main/res/anim/slide_in_bottom.xml
new file mode 100644
index 000000000..4d82e420a
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/slide_in_bottom.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/anim/slide_in_left.xml b/santa-tracker/src/main/res/anim/slide_in_left.xml
new file mode 100644
index 000000000..e4ae06688
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/slide_in_left.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/slide_out_right.xml b/santa-tracker/src/main/res/anim/slide_out_right.xml
new file mode 100644
index 000000000..c17825004
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/slide_out_right.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/anim/slide_out_top.xml b/santa-tracker/src/main/res/anim/slide_out_top.xml
new file mode 100644
index 000000000..726bb9d00
--- /dev/null
+++ b/santa-tracker/src/main/res/anim/slide_out_top.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_car.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_car.png
new file mode 100644
index 000000000..be5a93bec
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_car.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_jetpack.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_jetpack.png
new file mode 100644
index 000000000..0fd173772
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_elf_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_gumball_tilt.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_gumball_tilt.png
new file mode 100644
index 000000000..f77450e71
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_gumball_tilt.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_haywire_ride.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_haywire_ride.png
new file mode 100644
index 000000000..f0e637d92
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_haywire_ride.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_jingle_elves.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_jingle_elves.png
new file mode 100644
index 000000000..87130b1ab
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_jingle_elves.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_memory_match.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_memory_match.png
new file mode 100644
index 000000000..2d21382fe
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_memory_match.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_office_prank.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_office_prank.png
new file mode 100644
index 000000000..82dc5eac5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_office_prank.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santa_shake.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santa_shake.png
new file mode 100644
index 000000000..60fc0b12f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santa_shake.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_back.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_back.png
new file mode 100644
index 000000000..a427290a9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_back.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_takeoff.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_takeoff.png
new file mode 100644
index 000000000..edb03684d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_santas_takeoff.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_snowdown.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_snowdown.png
new file mode 100644
index 000000000..2674ce0ea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_snowdown.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_track_santa.png b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_track_santa.png
new file mode 100644
index 000000000..ac6201cd4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/android_game_cards_track_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_general.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_general.png
new file mode 100644
index 000000000..6b0ddec1b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_general.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_natgeo.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_natgeo.png
new file mode 100644
index 000000000..fd0414e01
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_natgeo.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_photo.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_photo.png
new file mode 100644
index 000000000..f21ab8238
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_santa.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_santa.png
new file mode 100644
index 000000000..75993f488
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_video.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_video.png
new file mode 100644
index 000000000..1491b7ae8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/avatar_worldfacts.png b/santa-tracker/src/main/res/drawable-hdpi/avatar_worldfacts.png
new file mode 100644
index 000000000..9bdae31fa
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/avatar_worldfacts.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/blue_100.png b/santa-tracker/src/main/res/drawable-hdpi/blue_100.png
new file mode 100644
index 000000000..1453b829f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/blue_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/blue_25.png b/santa-tracker/src/main/res/drawable-hdpi/blue_25.png
new file mode 100644
index 000000000..c27d71200
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/blue_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/blue_50.png b/santa-tracker/src/main/res/drawable-hdpi/blue_50.png
new file mode 100644
index 000000000..4f6fb1bac
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/blue_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/blue_75.png b/santa-tracker/src/main/res/drawable-hdpi/blue_75.png
new file mode 100644
index 000000000..e0f97d7a4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/blue_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/countdown_box_end.png b/santa-tracker/src/main/res/drawable-hdpi/countdown_box_end.png
new file mode 100644
index 000000000..d0ec0bf72
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/countdown_box_end.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/countdown_box_middle.png b/santa-tracker/src/main/res/drawable-hdpi/countdown_box_middle.png
new file mode 100644
index 000000000..2135b8f60
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/countdown_box_middle.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar.png b/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar.png
new file mode 100644
index 000000000..bcc5f51a7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar_pressed.png b/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..64ab3b2cf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_cancelbar_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_fab_achievements.png b/santa-tracker/src/main/res/drawable-hdpi/games_fab_achievements.png
new file mode 100644
index 000000000..452f4cb41
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_fab_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_fab_leaderboards.png b/santa-tracker/src/main/res/drawable-hdpi/games_fab_leaderboards.png
new file mode 100644
index 000000000..a23263638
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_fab_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements.png b/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements.png
new file mode 100644
index 000000000..6db433a13
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements_pressed.png b/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements_pressed.png
new file mode 100644
index 000000000..ea97b5ed6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_ic_achievements_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards.png b/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards.png
new file mode 100644
index 000000000..c3e8d521c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards_pressed.png b/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards_pressed.png
new file mode 100644
index 000000000..9c6f82abd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/games_ic_leaderboards_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_inner.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_inner.png
new file mode 100644
index 000000000..3978ced4a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_inner.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png
new file mode 100644
index 000000000..14bce07c4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_bottom.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..a30e1bee3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.png
new file mode 100644
index 000000000..880ba55fb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.png
new file mode 100644
index 000000000..3b1e133cb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_collected_disabled.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.png
new file mode 100644
index 000000000..d4a9dc8ff
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_indicator_pending.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..3192c57a2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_gumball_outlet.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_dialog.9.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_dialog.9.png
new file mode 100644
index 000000000..35e865e31
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_dialog.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_elf.png b/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_elf.png
new file mode 100644
index 000000000..99fa7a5c8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/gbg_score_summary_elf.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/google_logo.png b/santa-tracker/src/main/res/drawable-hdpi/google_logo.png
new file mode 100644
index 000000000..d3d38e010
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/google_logo.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/green_100.png b/santa-tracker/src/main/res/drawable-hdpi/green_100.png
new file mode 100644
index 000000000..7c04f80ee
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/green_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/green_25.png b/santa-tracker/src/main/res/drawable-hdpi/green_25.png
new file mode 100644
index 000000000..09d77a292
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/green_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/green_50.png b/santa-tracker/src/main/res/drawable-hdpi/green_50.png
new file mode 100644
index 000000000..d697da971
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/green_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/green_75.png b/santa-tracker/src/main/res/drawable-hdpi/green_75.png
new file mode 100644
index 000000000..9f736018c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/green_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ic_arrival.png b/santa-tracker/src/main/res/drawable-hdpi/ic_arrival.png
new file mode 100644
index 000000000..d3ffaa3ad
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ic_arrival.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ic_arrow_upward_white_36dp.png b/santa-tracker/src/main/res/drawable-hdpi/ic_arrow_upward_white_36dp.png
new file mode 100644
index 000000000..ce42f364b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ic_arrow_upward_white_36dp.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ic_cam.png b/santa-tracker/src/main/res/drawable-hdpi/ic_cam.png
new file mode 100644
index 000000000..fd4b0aad4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ic_cam.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ic_launcher_santa.png b/santa-tracker/src/main/res/drawable-hdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..9ffed676e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ic_weather.png b/santa-tracker/src/main/res/drawable-hdpi/ic_weather.png
new file mode 100644
index 000000000..83276bce2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ic_weather.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/icon_cast_controller.png b/santa-tracker/src/main/res/drawable-hdpi/icon_cast_controller.png
new file mode 100644
index 000000000..6b25fc720
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/icon_cast_controller.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_1.png b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_1.png
new file mode 100644
index 000000000..8218c95dd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_2.png b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_2.png
new file mode 100644
index 000000000..82a01139c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_3.png b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_3.png
new file mode 100644
index 000000000..c55ef79ed
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/instructions_shake_3.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_1.png b/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_1.png
new file mode 100644
index 000000000..402e59246
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_2.png b/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_2.png
new file mode 100644
index 000000000..131f9f20b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/instructions_touch_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/locked.png b/santa-tracker/src/main/res/drawable-hdpi/locked.png
new file mode 100644
index 000000000..0a8a5d8bd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/map_infowindow_popup.9.png b/santa-tracker/src/main/res/drawable-hdpi/map_infowindow_popup.9.png
new file mode 100644
index 000000000..8fd63d0c9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/map_infowindow_popup.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_dancer.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_dancer.png
new file mode 100644
index 000000000..dde85f4b7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_dancer.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_gumball.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_gumball.png
new file mode 100644
index 000000000..f62ee172e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_jetpack.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_jetpack.png
new file mode 100644
index 000000000..7cab02b60
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_15.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_15.png
new file mode 100644
index 000000000..573363e20
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_15.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_23.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_23.png
new file mode 100644
index 000000000..5bed8accc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_locked_23.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_memory.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_memory.png
new file mode 100644
index 000000000..4b760f373
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_rocket.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_rocket.png
new file mode 100644
index 000000000..8e91f82d3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_rocket.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_santa.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_santa.png
new file mode 100644
index 000000000..682038775
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_snowdown.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_snowdown.png
new file mode 100644
index 000000000..19cb81cba
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_snowdown.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_badge_video.png b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_video.png
new file mode 100644
index 000000000..27c58a7a5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_badge_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_memory.png b/santa-tracker/src/main/res/drawable-hdpi/marker_memory.png
new file mode 100644
index 000000000..00a068d16
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_memory_locked.png b/santa-tracker/src/main/res/drawable-hdpi/marker_memory_locked.png
new file mode 100644
index 000000000..fd1c2edc1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_memory_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_pin.png b/santa-tracker/src/main/res/drawable-hdpi/marker_pin.png
new file mode 100644
index 000000000..96d5ead20
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_pin.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_pin_blue.png b/santa-tracker/src/main/res/drawable-hdpi/marker_pin_blue.png
new file mode 100644
index 000000000..6634c3261
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_pin_blue.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_pin_light.png b/santa-tracker/src/main/res/drawable-hdpi/marker_pin_light.png
new file mode 100644
index 000000000..beae7ba94
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_pin_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents1.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents1.png
new file mode 100644
index 000000000..d93588ab2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents1.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents2.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents2.png
new file mode 100644
index 000000000..85cea861c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents2.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents3.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents3.png
new file mode 100644
index 000000000..cce81d82c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents3.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents4.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents4.png
new file mode 100644
index 000000000..38f592f4e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents4.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents5.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents5.png
new file mode 100644
index 000000000..0e74a8bdb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents5.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents6.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents6.png
new file mode 100644
index 000000000..dbd934e5e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents6.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents7.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents7.png
new file mode 100644
index 000000000..8c5cd7d64
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents7.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents8.png b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents8.png
new file mode 100644
index 000000000..c28a461aa
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_santa_presents8.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_selector.png b/santa-tracker/src/main/res/drawable-hdpi/marker_selector.png
new file mode 100644
index 000000000..44157c8f1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_selector.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/marker_selector_locked.png b/santa-tracker/src/main/res/drawable-hdpi/marker_selector_locked.png
new file mode 100644
index 000000000..2a0cc1faf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/marker_selector_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/misc_pause.png b/santa-tracker/src/main/res/drawable-hdpi/misc_pause.png
new file mode 100644
index 000000000..ee3450e1f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/misc_pause.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/misc_play.png b/santa-tracker/src/main/res/drawable-hdpi/misc_play.png
new file mode 100644
index 000000000..4fcdccb48
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/misc_play.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_background_left.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_left.png
new file mode 100644
index 000000000..7d59550dc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_background_right.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_right.png
new file mode 100644
index 000000000..07a5834ba
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_background_top.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_top.png
new file mode 100644
index 000000000..6e073790e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_background_top.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_ball.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_ball.png
new file mode 100644
index 000000000..7f9a37c1e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_ball.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_balloon.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_balloon.png
new file mode 100644
index 000000000..355466665
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_balloon.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_beachball.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_beachball.png
new file mode 100644
index 000000000..ddc237bc3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_beachball.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_candle.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_candle.png
new file mode 100644
index 000000000..859d9fbe4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_candle.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.png
new file mode 100644
index 000000000..6b7635b88
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_dark.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.png
new file mode 100644
index 000000000..4755435f8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_blue_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_orange.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_orange.png
new file mode 100644
index 000000000..974a230e5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_orange.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_purple.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_purple.png
new file mode 100644
index 000000000..facc52cfe
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_purple.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_red.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_red.png
new file mode 100644
index 000000000..553d06011
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_cloak_red.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_frame.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_frame.png
new file mode 100644
index 000000000..c499c970e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_frame.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_globe.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_globe.png
new file mode 100644
index 000000000..220ae6ee3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_globe.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_gumball.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_gumball.png
new file mode 100644
index 000000000..9defaae36
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_locked.jpg b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_locked.jpg
new file mode 100644
index 000000000..35e532925
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_locked.jpg differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_penguin.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_penguin.png
new file mode 100644
index 000000000..082b98c62
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_penguin.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_rabbit.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_rabbit.png
new file mode 100644
index 000000000..99d374bae
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_rabbit.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_reindeer.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_reindeer.png
new file mode 100644
index 000000000..9b1a53578
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_reindeer.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_snowman.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_snowman.png
new file mode 100644
index 000000000..e4a3b8722
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_snowman.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_tree.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_tree.png
new file mode 100644
index 000000000..b0175c160
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_tree.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_card_trophy.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_trophy.png
new file mode 100644
index 000000000..d4d509c77
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_card_trophy.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_left.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_left.png
new file mode 100644
index 000000000..97ec56629
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_right.png b/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_right.png
new file mode 100644
index 000000000..ea9a75f1a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mmg_pane_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png b/santa-tracker/src/main/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png
new file mode 100644
index 000000000..6d708f2f4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/notification_small.png b/santa-tracker/src/main/res/drawable-hdpi/notification_small.png
new file mode 100644
index 000000000..087bd376a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/notification_small.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/ornament.png b/santa-tracker/src/main/res/drawable-hdpi/ornament.png
new file mode 100644
index 000000000..66ae4c2a5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/ornament.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/purple_100.png b/santa-tracker/src/main/res/drawable-hdpi/purple_100.png
new file mode 100644
index 000000000..67561338b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/purple_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/purple_25.png b/santa-tracker/src/main/res/drawable-hdpi/purple_25.png
new file mode 100644
index 000000000..7a21028ad
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/purple_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/purple_50.png b/santa-tracker/src/main/res/drawable-hdpi/purple_50.png
new file mode 100644
index 000000000..589696f7f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/purple_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/purple_75.png b/santa-tracker/src/main/res/drawable-hdpi/purple_75.png
new file mode 100644
index 000000000..db74f3d57
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/purple_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/red_100.png b/santa-tracker/src/main/res/drawable-hdpi/red_100.png
new file mode 100644
index 000000000..8c29ce0e5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/red_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/red_25.png b/santa-tracker/src/main/res/drawable-hdpi/red_25.png
new file mode 100644
index 000000000..3e2f2b55a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/red_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/red_50.png b/santa-tracker/src/main/res/drawable-hdpi/red_50.png
new file mode 100644
index 000000000..5b8d57204
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/red_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/red_75.png b/santa-tracker/src/main/res/drawable-hdpi/red_75.png
new file mode 100644
index 000000000..61846b778
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/red_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_arm.png b/santa-tracker/src/main/res/drawable-hdpi/santa_arm.png
new file mode 100644
index 000000000..6473d4b9f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_arm.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_body.png b/santa-tracker/src/main/res/drawable-hdpi/santa_body.png
new file mode 100644
index 000000000..c781402b8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_body.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_e.png b/santa-tracker/src/main/res/drawable-hdpi/santa_e.png
new file mode 100644
index 000000000..3c5d13f85
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_e.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_n.png b/santa-tracker/src/main/res/drawable-hdpi/santa_n.png
new file mode 100644
index 000000000..4b8a9c03b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_n.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_ne.png b/santa-tracker/src/main/res/drawable-hdpi/santa_ne.png
new file mode 100644
index 000000000..6fdeb4409
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_ne.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_nw.png b/santa-tracker/src/main/res/drawable-hdpi/santa_nw.png
new file mode 100644
index 000000000..937c3e5e4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_nw.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_s.png b/santa-tracker/src/main/res/drawable-hdpi/santa_s.png
new file mode 100644
index 000000000..6093f12e7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_s.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_se.png b/santa-tracker/src/main/res/drawable-hdpi/santa_se.png
new file mode 100644
index 000000000..dd913d8d1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_se.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_sw.png b/santa-tracker/src/main/res/drawable-hdpi/santa_sw.png
new file mode 100644
index 000000000..9499140c8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_sw.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santa_w.png b/santa-tracker/src/main/res/drawable-hdpi/santa_w.png
new file mode 100644
index 000000000..2ee1e5ee1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santa_w.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santacam.png b/santa-tracker/src/main/res/drawable-hdpi/santacam.png
new file mode 100644
index 000000000..7f1432c77
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santacam.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/santatracker_logo_startup.png b/santa-tracker/src/main/res/drawable-hdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..c33ed2c2e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/yellow_100.png b/santa-tracker/src/main/res/drawable-hdpi/yellow_100.png
new file mode 100644
index 000000000..69ef6752c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/yellow_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/yellow_25.png b/santa-tracker/src/main/res/drawable-hdpi/yellow_25.png
new file mode 100644
index 000000000..4768e5ee0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/yellow_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/yellow_50.png b/santa-tracker/src/main/res/drawable-hdpi/yellow_50.png
new file mode 100644
index 000000000..cd9917711
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/yellow_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-hdpi/yellow_75.png b/santa-tracker/src/main/res/drawable-hdpi/yellow_75.png
new file mode 100644
index 000000000..a144fdd54
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-hdpi/yellow_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-ldrtl/bg_copyright.xml b/santa-tracker/src/main/res/drawable-ldrtl/bg_copyright.xml
new file mode 100644
index 000000000..4d069094b
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable-ldrtl/bg_copyright.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_car.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_car.png
new file mode 100644
index 000000000..36b19c6cb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_car.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_jetpack.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_jetpack.png
new file mode 100644
index 000000000..47d554390
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_elf_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_gumball_tilt.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_gumball_tilt.png
new file mode 100644
index 000000000..e5d3deb3d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_gumball_tilt.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_haywire_ride.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_haywire_ride.png
new file mode 100644
index 000000000..c8173db85
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_haywire_ride.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_jingle_elves.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_jingle_elves.png
new file mode 100644
index 000000000..cfe0396ae
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_jingle_elves.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_memory_match.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_memory_match.png
new file mode 100644
index 000000000..650c86822
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_memory_match.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_office_prank.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_office_prank.png
new file mode 100644
index 000000000..615e8c740
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_office_prank.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santa_shake.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santa_shake.png
new file mode 100644
index 000000000..78e33c559
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santa_shake.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_back.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_back.png
new file mode 100644
index 000000000..6a65cad56
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_back.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_takeoff.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_takeoff.png
new file mode 100644
index 000000000..a1b4d5bd4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_santas_takeoff.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_snowdown.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_snowdown.png
new file mode 100644
index 000000000..f9b454983
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_snowdown.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_track_santa.png b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_track_santa.png
new file mode 100644
index 000000000..f201ba81c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/android_game_cards_track_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_general.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_general.png
new file mode 100644
index 000000000..e62ea13c0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_general.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_natgeo.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_natgeo.png
new file mode 100644
index 000000000..b46bbf382
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_natgeo.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_photo.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_photo.png
new file mode 100644
index 000000000..592f0bfbf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_santa.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_santa.png
new file mode 100644
index 000000000..c7b4457bd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_video.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_video.png
new file mode 100644
index 000000000..6e6c674d8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/avatar_worldfacts.png b/santa-tracker/src/main/res/drawable-mdpi/avatar_worldfacts.png
new file mode 100644
index 000000000..79eb72e84
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/avatar_worldfacts.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/blue_100.png b/santa-tracker/src/main/res/drawable-mdpi/blue_100.png
new file mode 100644
index 000000000..cbd7681d0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/blue_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/blue_25.png b/santa-tracker/src/main/res/drawable-mdpi/blue_25.png
new file mode 100644
index 000000000..ec9ef11db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/blue_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/blue_50.png b/santa-tracker/src/main/res/drawable-mdpi/blue_50.png
new file mode 100644
index 000000000..82b4a559b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/blue_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/blue_75.png b/santa-tracker/src/main/res/drawable-mdpi/blue_75.png
new file mode 100644
index 000000000..0de7bb8e6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/blue_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/countdown_box_end.png b/santa-tracker/src/main/res/drawable-mdpi/countdown_box_end.png
new file mode 100644
index 000000000..e2ffe68a2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/countdown_box_end.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/countdown_box_middle.png b/santa-tracker/src/main/res/drawable-mdpi/countdown_box_middle.png
new file mode 100644
index 000000000..a91c0457d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/countdown_box_middle.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar.png b/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar.png
new file mode 100644
index 000000000..765fd14cc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar_pressed.png b/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..24b8550e3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_cancelbar_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_fab_achievements.png b/santa-tracker/src/main/res/drawable-mdpi/games_fab_achievements.png
new file mode 100644
index 000000000..0b0ff0f6f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_fab_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_fab_leaderboards.png b/santa-tracker/src/main/res/drawable-mdpi/games_fab_leaderboards.png
new file mode 100644
index 000000000..a43934344
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_fab_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements.png b/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements.png
new file mode 100644
index 000000000..570cd236d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements_pressed.png b/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements_pressed.png
new file mode 100644
index 000000000..38f1fe52c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_ic_achievements_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards.png b/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards.png
new file mode 100644
index 000000000..2091e79db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards_pressed.png b/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards_pressed.png
new file mode 100644
index 000000000..9dae1aecc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/games_ic_leaderboards_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_inner.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_inner.png
new file mode 100644
index 000000000..6af35da5e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_inner.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.png
new file mode 100644
index 000000000..df30bfe15
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_bottom.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.png
new file mode 100644
index 000000000..c1b064d44
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_bg_main_outer_top.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.png
new file mode 100644
index 000000000..881c59a89
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.png
new file mode 100644
index 000000000..9ad49e258
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_collected_disabled.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.png
new file mode 100644
index 000000000..98cac3b53
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_indicator_pending.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..8d1da1eae
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_gumball_outlet.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_dialog.9.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_dialog.9.png
new file mode 100644
index 000000000..7a5c8cc07
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_dialog.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_elf.png b/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_elf.png
new file mode 100644
index 000000000..03a824aae
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/gbg_score_summary_elf.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/google_logo.png b/santa-tracker/src/main/res/drawable-mdpi/google_logo.png
new file mode 100644
index 000000000..9129912db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/google_logo.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/green_100.png b/santa-tracker/src/main/res/drawable-mdpi/green_100.png
new file mode 100644
index 000000000..2cc280609
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/green_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/green_25.png b/santa-tracker/src/main/res/drawable-mdpi/green_25.png
new file mode 100644
index 000000000..a83513c08
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/green_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/green_50.png b/santa-tracker/src/main/res/drawable-mdpi/green_50.png
new file mode 100644
index 000000000..06b69b0ed
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/green_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/green_75.png b/santa-tracker/src/main/res/drawable-mdpi/green_75.png
new file mode 100644
index 000000000..8e3b6f012
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/green_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ic_arrival.png b/santa-tracker/src/main/res/drawable-mdpi/ic_arrival.png
new file mode 100644
index 000000000..1c03edf70
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ic_arrival.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ic_arrow_upward_white_36dp.png b/santa-tracker/src/main/res/drawable-mdpi/ic_arrow_upward_white_36dp.png
new file mode 100644
index 000000000..c39725cba
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ic_arrow_upward_white_36dp.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ic_cam.png b/santa-tracker/src/main/res/drawable-mdpi/ic_cam.png
new file mode 100644
index 000000000..4c3701c83
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ic_cam.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ic_launcher_santa.png b/santa-tracker/src/main/res/drawable-mdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..1d507a3c0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ic_weather.png b/santa-tracker/src/main/res/drawable-mdpi/ic_weather.png
new file mode 100644
index 000000000..dd0f9e148
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ic_weather.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_1.png b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_1.png
new file mode 100644
index 000000000..0614b5e3c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_2.png b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_2.png
new file mode 100644
index 000000000..5a0f161a4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_3.png b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_3.png
new file mode 100644
index 000000000..9851aef26
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/instructions_shake_3.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_1.png b/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_1.png
new file mode 100644
index 000000000..dde6dce5e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_2.png b/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_2.png
new file mode 100644
index 000000000..535e60752
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/instructions_touch_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/locked.png b/santa-tracker/src/main/res/drawable-mdpi/locked.png
new file mode 100644
index 000000000..64ab8fc6f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/map_infowindow_popup.9.png b/santa-tracker/src/main/res/drawable-mdpi/map_infowindow_popup.9.png
new file mode 100644
index 000000000..c050fd024
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/map_infowindow_popup.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_dancer.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_dancer.png
new file mode 100644
index 000000000..65bdf7c66
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_dancer.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_gumball.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_gumball.png
new file mode 100644
index 000000000..7bb73d8a2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_jetpack.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_jetpack.png
new file mode 100644
index 000000000..decb98d7a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_15.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_15.png
new file mode 100644
index 000000000..d7cfa2f04
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_15.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_23.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_23.png
new file mode 100644
index 000000000..d42ab92f6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_locked_23.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_memory.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_memory.png
new file mode 100644
index 000000000..d75118f2a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_rocket.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_rocket.png
new file mode 100644
index 000000000..6b133a3fb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_rocket.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_santa.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_santa.png
new file mode 100644
index 000000000..f65594284
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_badge_video.png b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_video.png
new file mode 100644
index 000000000..1de8abda6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_badge_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_memory.png b/santa-tracker/src/main/res/drawable-mdpi/marker_memory.png
new file mode 100644
index 000000000..6ed0ef0de
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_memory_locked.png b/santa-tracker/src/main/res/drawable-mdpi/marker_memory_locked.png
new file mode 100644
index 000000000..fe04cadbd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_memory_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_pin.png b/santa-tracker/src/main/res/drawable-mdpi/marker_pin.png
new file mode 100644
index 000000000..7c0d9bb22
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_pin.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_pin_blue.png b/santa-tracker/src/main/res/drawable-mdpi/marker_pin_blue.png
new file mode 100644
index 000000000..a6bf4b8ac
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_pin_blue.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_pin_light.png b/santa-tracker/src/main/res/drawable-mdpi/marker_pin_light.png
new file mode 100644
index 000000000..50bc4004f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_pin_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents1.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents1.png
new file mode 100644
index 000000000..dc182db44
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents1.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents2.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents2.png
new file mode 100644
index 000000000..6f98a0cac
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents2.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents3.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents3.png
new file mode 100644
index 000000000..d08a9182d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents3.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents4.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents4.png
new file mode 100644
index 000000000..7fb70c613
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents4.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents5.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents5.png
new file mode 100644
index 000000000..72ac6fd83
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents5.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents6.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents6.png
new file mode 100644
index 000000000..5b2d055d4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents6.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents7.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents7.png
new file mode 100644
index 000000000..eca8d8882
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents7.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents8.png b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents8.png
new file mode 100644
index 000000000..73fb946ad
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_santa_presents8.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_selector.png b/santa-tracker/src/main/res/drawable-mdpi/marker_selector.png
new file mode 100644
index 000000000..209fd65c9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_selector.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/marker_selector_locked.png b/santa-tracker/src/main/res/drawable-mdpi/marker_selector_locked.png
new file mode 100644
index 000000000..b98b562e4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/marker_selector_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/misc_pause.png b/santa-tracker/src/main/res/drawable-mdpi/misc_pause.png
new file mode 100644
index 000000000..d3e1f56ed
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/misc_pause.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/misc_play.png b/santa-tracker/src/main/res/drawable-mdpi/misc_play.png
new file mode 100644
index 000000000..7bf303124
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/misc_play.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_background_left.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_left.png
new file mode 100644
index 000000000..cb7a40328
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_background_right.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_right.png
new file mode 100644
index 000000000..b89190568
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_background_top.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_top.png
new file mode 100644
index 000000000..ce021f6d8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_background_top.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_ball.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_ball.png
new file mode 100644
index 000000000..d76347e24
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_ball.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_balloon.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_balloon.png
new file mode 100644
index 000000000..1799a9cb4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_balloon.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_beachball.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_beachball.png
new file mode 100644
index 000000000..6ac741291
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_beachball.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_candle.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_candle.png
new file mode 100644
index 000000000..eef2f2705
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_candle.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.png
new file mode 100644
index 000000000..970fcbfa9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_dark.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.png
new file mode 100644
index 000000000..033fd36d0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_blue_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_orange.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_orange.png
new file mode 100644
index 000000000..17a7bb875
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_orange.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_purple.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_purple.png
new file mode 100644
index 000000000..c015b104a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_purple.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_red.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_red.png
new file mode 100644
index 000000000..dd913a79c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_cloak_red.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_frame.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_frame.png
new file mode 100644
index 000000000..10527b06b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_frame.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_globe.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_globe.png
new file mode 100644
index 000000000..a502f2f74
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_globe.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_gumball.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_gumball.png
new file mode 100644
index 000000000..833817195
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_locked.jpg b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_locked.jpg
new file mode 100644
index 000000000..1a64b18ea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_locked.jpg differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_penguin.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_penguin.png
new file mode 100644
index 000000000..f9d382f73
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_penguin.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_rabbit.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_rabbit.png
new file mode 100644
index 000000000..529b11205
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_rabbit.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_reindeer.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_reindeer.png
new file mode 100644
index 000000000..f52842707
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_reindeer.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_snowman.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_snowman.png
new file mode 100644
index 000000000..b21ef666d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_snowman.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_tree.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_tree.png
new file mode 100644
index 000000000..9fb02d210
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_tree.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_card_trophy.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_trophy.png
new file mode 100644
index 000000000..0cf02f1b1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_card_trophy.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_left.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_left.png
new file mode 100644
index 000000000..4ab6e7470
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_right.png b/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_right.png
new file mode 100644
index 000000000..988564d07
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mmg_pane_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png b/santa-tracker/src/main/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png
new file mode 100644
index 000000000..c52e61b66
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/notification_small.png b/santa-tracker/src/main/res/drawable-mdpi/notification_small.png
new file mode 100644
index 000000000..c5441c353
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/notification_small.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/ornament.png b/santa-tracker/src/main/res/drawable-mdpi/ornament.png
new file mode 100644
index 000000000..7b7dcc37b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/ornament.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/purple_100.png b/santa-tracker/src/main/res/drawable-mdpi/purple_100.png
new file mode 100644
index 000000000..c8e72b765
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/purple_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/purple_25.png b/santa-tracker/src/main/res/drawable-mdpi/purple_25.png
new file mode 100644
index 000000000..c09dc39df
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/purple_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/purple_50.png b/santa-tracker/src/main/res/drawable-mdpi/purple_50.png
new file mode 100644
index 000000000..86b3d2aac
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/purple_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/purple_75.png b/santa-tracker/src/main/res/drawable-mdpi/purple_75.png
new file mode 100644
index 000000000..44a43e2c0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/purple_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/red_100.png b/santa-tracker/src/main/res/drawable-mdpi/red_100.png
new file mode 100644
index 000000000..216288a09
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/red_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/red_25.png b/santa-tracker/src/main/res/drawable-mdpi/red_25.png
new file mode 100644
index 000000000..d28e43cd9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/red_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/red_50.png b/santa-tracker/src/main/res/drawable-mdpi/red_50.png
new file mode 100644
index 000000000..ae7d52b0b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/red_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/red_75.png b/santa-tracker/src/main/res/drawable-mdpi/red_75.png
new file mode 100644
index 000000000..811a91ce5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/red_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_arm.png b/santa-tracker/src/main/res/drawable-mdpi/santa_arm.png
new file mode 100644
index 000000000..924cb4041
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_arm.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_body.png b/santa-tracker/src/main/res/drawable-mdpi/santa_body.png
new file mode 100644
index 000000000..c333b2144
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_body.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_e.png b/santa-tracker/src/main/res/drawable-mdpi/santa_e.png
new file mode 100644
index 000000000..d8a7029a3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_e.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_n.png b/santa-tracker/src/main/res/drawable-mdpi/santa_n.png
new file mode 100644
index 000000000..1a943d5e9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_n.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_ne.png b/santa-tracker/src/main/res/drawable-mdpi/santa_ne.png
new file mode 100644
index 000000000..ec832f269
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_ne.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_nw.png b/santa-tracker/src/main/res/drawable-mdpi/santa_nw.png
new file mode 100644
index 000000000..87a1ddd36
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_nw.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_s.png b/santa-tracker/src/main/res/drawable-mdpi/santa_s.png
new file mode 100644
index 000000000..f668d7efd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_s.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_se.png b/santa-tracker/src/main/res/drawable-mdpi/santa_se.png
new file mode 100644
index 000000000..10ac3aed1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_se.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_sw.png b/santa-tracker/src/main/res/drawable-mdpi/santa_sw.png
new file mode 100644
index 000000000..837db5633
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_sw.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santa_w.png b/santa-tracker/src/main/res/drawable-mdpi/santa_w.png
new file mode 100644
index 000000000..c64c4847c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santa_w.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santacam.png b/santa-tracker/src/main/res/drawable-mdpi/santacam.png
new file mode 100644
index 000000000..d6cb7c222
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santacam.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/santatracker_logo_startup.png b/santa-tracker/src/main/res/drawable-mdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..557d1d0db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/yellow_100.png b/santa-tracker/src/main/res/drawable-mdpi/yellow_100.png
new file mode 100644
index 000000000..941fe0cc0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/yellow_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/yellow_25.png b/santa-tracker/src/main/res/drawable-mdpi/yellow_25.png
new file mode 100644
index 000000000..b649660ad
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/yellow_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/yellow_50.png b/santa-tracker/src/main/res/drawable-mdpi/yellow_50.png
new file mode 100644
index 000000000..6457e589c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/yellow_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-mdpi/yellow_75.png b/santa-tracker/src/main/res/drawable-mdpi/yellow_75.png
new file mode 100644
index 000000000..cf2251987
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-mdpi/yellow_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_bigplay.png b/santa-tracker/src/main/res/drawable-nodpi/games_bigplay.png
new file mode 100644
index 000000000..8b1837e72
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_bigplay.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_bigplay_pressed.png b/santa-tracker/src/main/res/drawable-nodpi/games_bigplay_pressed.png
new file mode 100644
index 000000000..40fad1371
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_bigplay_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_0.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_0.png
new file mode 100644
index 000000000..c7d733cf3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_0.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_1.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_1.png
new file mode 100644
index 000000000..74d6aad23
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_2.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_2.png
new file mode 100644
index 000000000..cc8cc8812
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_3.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_3.png
new file mode 100644
index 000000000..cab843aea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_3.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_4.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_4.png
new file mode 100644
index 000000000..ce96dacee
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_4.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_5.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_5.png
new file mode 100644
index 000000000..3cd6d4995
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_5.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_6.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_6.png
new file mode 100644
index 000000000..36fe120d1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_6.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_7.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_7.png
new file mode 100644
index 000000000..a114131d3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_7.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_8.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_8.png
new file mode 100644
index 000000000..808014990
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_8.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_digit_9.png b/santa-tracker/src/main/res/drawable-nodpi/games_digit_9.png
new file mode 100644
index 000000000..8d0906f1c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_digit_9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_pause.png b/santa-tracker/src/main/res/drawable-nodpi/games_pause.png
new file mode 100644
index 000000000..daa9e934c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_pause.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_pause_pressed.png b/santa-tracker/src/main/res/drawable-nodpi/games_pause_pressed.png
new file mode 100644
index 000000000..b6c5220a9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_pause_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_play.png b/santa-tracker/src/main/res/drawable-nodpi/games_play.png
new file mode 100644
index 000000000..589501077
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_play.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_play_pressed.png b/santa-tracker/src/main/res/drawable-nodpi/games_play_pressed.png
new file mode 100644
index 000000000..81ee60d54
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_play_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/games_scorebar.png b/santa-tracker/src/main/res/drawable-nodpi/games_scorebar.png
new file mode 100644
index 000000000..501a865ee
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/games_scorebar.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png
new file mode 100644
index 000000000..c21aca11c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_bg_gumball_indicatorp.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1280.9.png
new file mode 100644
index 000000000..45acce325
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png
new file mode 100644
index 000000000..5b78456d2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png
new file mode 100644
index 000000000..521a95a10
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png
new file mode 100644
index 000000000..7a3a34ca5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1280.9.png
new file mode 100644
index 000000000..94571d34d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png
new file mode 100644
index 000000000..47c5d806f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png
new file mode 100644
index 000000000..7ea241be5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png
new file mode 100644
index 000000000..c336a6044
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_end_reverse_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1280.9.png
new file mode 100644
index 000000000..8cc55a34d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png
new file mode 100644
index 000000000..1f81e8379
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png
new file mode 100644
index 000000000..6dc0eaede
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png
new file mode 100644
index 000000000..52c2b0951
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1280.9.png
new file mode 100644
index 000000000..6beff47bd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png
new file mode 100644
index 000000000..7ac298993
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png
new file mode 100644
index 000000000..351a38364
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png
new file mode 100644
index 000000000..8313ef24c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_hook_reverse_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six.png
new file mode 100644
index 000000000..e5f207758
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_large_angle_six.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1280.9.png
new file mode 100644
index 000000000..d7a557fab
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png
new file mode 100644
index 000000000..2da631978
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png
new file mode 100644
index 000000000..f71e33910
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png
new file mode 100644
index 000000000..5872369e7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine.png
new file mode 100644
index 000000000..2cf1e176f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_angle_nine.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1280.9.png
new file mode 100644
index 000000000..4477dc04c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png
new file mode 100644
index 000000000..7c7d7e543
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png
new file mode 100644
index 000000000..3d3279d1d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png
new file mode 100644
index 000000000..7718a7de5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_main_reverse_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six.png
new file mode 100644
index 000000000..78e256c93
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_med_angle_six.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six.png
new file mode 100644
index 000000000..f95f5fd1c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_six.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve.png
new file mode 100644
index 000000000..185208049
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_small_angle_twelve.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six.png
new file mode 100644
index 000000000..91f6058a8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_candycane_tiny_reverse_angle_six.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1280.9.png
new file mode 100644
index 000000000..d20593a6b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png
new file mode 100644
index 000000000..abbf05b04
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png
new file mode 100644
index 000000000..937daf378
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png
new file mode 100644
index 000000000..2f558b897
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_blue_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1280.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1280.png
new file mode 100644
index 000000000..d1231a1d2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1280.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.png
new file mode 100644
index 000000000..ef1917040
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_1920.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.png
new file mode 100644
index 000000000..bda5b5d4b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_480.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.png
new file mode 100644
index 000000000..350171917
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_funnel_800.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1280.9.png
new file mode 100644
index 000000000..e5b652698
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png
new file mode 100644
index 000000000..93db61f47
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png
new file mode 100644
index 000000000..de71c50d0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png
new file mode 100644
index 000000000..c339fb6e3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_green_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1280.9.png
new file mode 100644
index 000000000..1c8be0754
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png
new file mode 100644
index 000000000..92424a24d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png
new file mode 100644
index 000000000..9c7aa6257
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png
new file mode 100644
index 000000000..db95bf193
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_orange_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1280.9.png
new file mode 100644
index 000000000..3611291c2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png
new file mode 100644
index 000000000..1bcc20032
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png
new file mode 100644
index 000000000..bad809490
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png
new file mode 100644
index 000000000..debd3b0c3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_purple_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1280.9.png
new file mode 100644
index 000000000..798730dab
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png
new file mode 100644
index 000000000..478463caf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png
new file mode 100644
index 000000000..120ef93d1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png
new file mode 100644
index 000000000..ce2a871bc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_red_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1280.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1280.9.png
new file mode 100644
index 000000000..ce129cbb7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1280.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png
new file mode 100644
index 000000000..ed7a21afc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_1920.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png
new file mode 100644
index 000000000..d627a8496
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_480.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png
new file mode 100644
index 000000000..71f91da81
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/gbg_gumball_yellow_800.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy1.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy1.png
new file mode 100644
index 000000000..2285fcd44
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy1.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy2.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy2.png
new file mode 100644
index 000000000..ed4e7b34f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy2.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy3.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy3.png
new file mode 100644
index 000000000..3b41bdb4b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy3.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy4.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy4.png
new file mode 100644
index 000000000..303bc6062
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_candy4.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_clock.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_clock.png
new file mode 100644
index 000000000..87e330147
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_clock.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_cloud.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_cloud.png
new file mode 100644
index 000000000..0e7cf9e66
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_cloud.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_2x.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_2x.png
new file mode 100644
index 000000000..5ed77a540
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_2x.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_3x.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_3x.png
new file mode 100644
index 000000000..b7a86de56
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_3x.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_4x.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_4x.png
new file mode 100644
index 000000000..bd039c13a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_combo_4x.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire.png
new file mode 100644
index 000000000..e19046535
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire1.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire1.png
new file mode 100644
index 000000000..7b089a6f2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire1.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire2.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire2.png
new file mode 100644
index 000000000..89512aa92
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_fire2.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_playagain.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_playagain.png
new file mode 100644
index 000000000..45404b341
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_playagain.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_player.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_player.png
new file mode 100644
index 000000000..9b62338b0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_player.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_podium.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_podium.png
new file mode 100644
index 000000000..319fe25c4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_podium.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_present1.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present1.png
new file mode 100644
index 000000000..066f4d1c0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present1.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_present2.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present2.png
new file mode 100644
index 000000000..a9227c3f2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present2.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_present3.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present3.png
new file mode 100644
index 000000000..1653149a3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present3.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_present4.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present4.png
new file mode 100644
index 000000000..42c230ea0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_present4.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_score100.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_score100.png
new file mode 100644
index 000000000..f43d287ea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_score100.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin.png
new file mode 100644
index 000000000..4b64632ff
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin_pressed.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin_pressed.png
new file mode 100644
index 000000000..0f9ed2527
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_signin_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_small1.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small1.png
new file mode 100644
index 000000000..6d5e1109b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small1.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_small2.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small2.png
new file mode 100644
index 000000000..458b491ed
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small2.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_small3.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small3.png
new file mode 100644
index 000000000..d9e65bb73
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small3.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/jetpack_small4.png b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small4.png
new file mode 100644
index 000000000..8caa8b6dc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/jetpack_small4.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/location_photo.png b/santa-tracker/src/main/res/drawable-nodpi/location_photo.png
new file mode 100644
index 000000000..56fbb677f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/location_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/santa_info_notification_background.png b/santa-tracker/src/main/res/drawable-nodpi/santa_info_notification_background.png
new file mode 100644
index 000000000..a9f952196
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/santa_info_notification_background.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/santa_notification_background.png b/santa-tracker/src/main/res/drawable-nodpi/santa_notification_background.png
new file mode 100644
index 000000000..16b4a937f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/santa_notification_background.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/santatracker_logo_banner.png b/santa-tracker/src/main/res/drawable-nodpi/santatracker_logo_banner.png
new file mode 100644
index 000000000..9f67b715d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/santatracker_logo_banner.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/snowman.png b/santa-tracker/src/main/res/drawable-nodpi/snowman.png
new file mode 100644
index 000000000..c12f7a830
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/snowman.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/staticmap.png b/santa-tracker/src/main/res/drawable-nodpi/staticmap.png
new file mode 100644
index 000000000..3fa3d706e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/staticmap.png differ
diff --git a/santa-tracker/src/main/res/drawable-nodpi/transparent.png b/santa-tracker/src/main/res/drawable-nodpi/transparent.png
new file mode 100644
index 000000000..cc4b8d91a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-nodpi/transparent.png differ
diff --git a/santa-tracker/src/main/res/drawable-v21/bg_top.xml b/santa-tracker/src/main/res/drawable-v21/bg_top.xml
new file mode 100644
index 000000000..48fee9b73
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable-v21/bg_top.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable-v21/card_ripple.xml b/santa-tracker/src/main/res/drawable-v21/card_ripple.xml
new file mode 100644
index 000000000..0b885eb1b
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable-v21/card_ripple.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_car.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_car.png
new file mode 100644
index 000000000..6a18dd19f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_car.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_jetpack.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_jetpack.png
new file mode 100644
index 000000000..b39719298
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_elf_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_gumball_tilt.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_gumball_tilt.png
new file mode 100644
index 000000000..162f72fb2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_gumball_tilt.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_haywire_ride.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_haywire_ride.png
new file mode 100644
index 000000000..c5717f76f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_haywire_ride.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_jingle_elves.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_jingle_elves.png
new file mode 100644
index 000000000..0746f2940
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_jingle_elves.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_memory_match.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_memory_match.png
new file mode 100644
index 000000000..851cbe5ca
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_memory_match.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_office_prank.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_office_prank.png
new file mode 100644
index 000000000..c7cdf7d2b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_office_prank.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santa_shake.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santa_shake.png
new file mode 100644
index 000000000..9ec339d94
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santa_shake.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_back.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_back.png
new file mode 100644
index 000000000..194091415
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_back.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_takeoff.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_takeoff.png
new file mode 100644
index 000000000..05e3b7f48
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_santas_takeoff.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_snowdown.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_snowdown.png
new file mode 100644
index 000000000..307ce723e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_snowdown.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_track_santa.png b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_track_santa.png
new file mode 100644
index 000000000..f4887c322
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/android_game_cards_track_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_general.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_general.png
new file mode 100644
index 000000000..8cd773f46
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_general.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_natgeo.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_natgeo.png
new file mode 100644
index 000000000..f8ebaaa5b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_natgeo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_photo.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_photo.png
new file mode 100644
index 000000000..84f776ae8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_santa.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_santa.png
new file mode 100644
index 000000000..fbd0b4646
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_video.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_video.png
new file mode 100644
index 000000000..40c47e26d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/avatar_worldfacts.png b/santa-tracker/src/main/res/drawable-xhdpi/avatar_worldfacts.png
new file mode 100644
index 000000000..637a9d399
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/avatar_worldfacts.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/blue_100.png b/santa-tracker/src/main/res/drawable-xhdpi/blue_100.png
new file mode 100644
index 000000000..9f57e74a5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/blue_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/blue_25.png b/santa-tracker/src/main/res/drawable-xhdpi/blue_25.png
new file mode 100644
index 000000000..3d977fb6d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/blue_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/blue_50.png b/santa-tracker/src/main/res/drawable-xhdpi/blue_50.png
new file mode 100644
index 000000000..211481ab3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/blue_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/blue_75.png b/santa-tracker/src/main/res/drawable-xhdpi/blue_75.png
new file mode 100644
index 000000000..c6cb7a742
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/blue_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_end.png b/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_end.png
new file mode 100644
index 000000000..5e787e9ee
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_end.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_middle.png b/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_middle.png
new file mode 100644
index 000000000..afda39bef
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/countdown_box_middle.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar.png b/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar.png
new file mode 100644
index 000000000..c071816e2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png b/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..11bcdee08
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_cancelbar_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_fab_achievements.png b/santa-tracker/src/main/res/drawable-xhdpi/games_fab_achievements.png
new file mode 100644
index 000000000..1f26a7c06
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_fab_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_fab_leaderboards.png b/santa-tracker/src/main/res/drawable-xhdpi/games_fab_leaderboards.png
new file mode 100644
index 000000000..9bda7e8cd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_fab_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements.png b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements.png
new file mode 100644
index 000000000..484306fe6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements_pressed.png b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements_pressed.png
new file mode 100644
index 000000000..414819779
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_achievements_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards.png b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards.png
new file mode 100644
index 000000000..7b302e34e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards_pressed.png b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards_pressed.png
new file mode 100644
index 000000000..ef2191bd5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/games_ic_leaderboards_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_inner.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_inner.png
new file mode 100644
index 000000000..a5fc7e4f0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_inner.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png
new file mode 100644
index 000000000..d31b5d12f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_bottom.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..78984f6d7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.png
new file mode 100644
index 000000000..472cc97da
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.png
new file mode 100644
index 000000000..21119e71d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_collected_disabled.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.png
new file mode 100644
index 000000000..eec026769
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_indicator_pending.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..f1d7de12a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_gumball_outlet.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_dialog.9.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_dialog.9.png
new file mode 100644
index 000000000..136d3da4a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_dialog.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_elf.png b/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_elf.png
new file mode 100644
index 000000000..2c3f66dbd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/gbg_score_summary_elf.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/google_logo.png b/santa-tracker/src/main/res/drawable-xhdpi/google_logo.png
new file mode 100644
index 000000000..df2c44250
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/google_logo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/green_100.png b/santa-tracker/src/main/res/drawable-xhdpi/green_100.png
new file mode 100644
index 000000000..a8c950acc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/green_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/green_25.png b/santa-tracker/src/main/res/drawable-xhdpi/green_25.png
new file mode 100644
index 000000000..24f56240d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/green_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/green_50.png b/santa-tracker/src/main/res/drawable-xhdpi/green_50.png
new file mode 100644
index 000000000..b28dc51c1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/green_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/green_75.png b/santa-tracker/src/main/res/drawable-xhdpi/green_75.png
new file mode 100644
index 000000000..bfd71689f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/green_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ic_arrival.png b/santa-tracker/src/main/res/drawable-xhdpi/ic_arrival.png
new file mode 100644
index 000000000..4fd271508
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ic_arrival.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ic_arrow_upward_white_36dp.png b/santa-tracker/src/main/res/drawable-xhdpi/ic_arrow_upward_white_36dp.png
new file mode 100644
index 000000000..d7b27da82
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ic_arrow_upward_white_36dp.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ic_cam.png b/santa-tracker/src/main/res/drawable-xhdpi/ic_cam.png
new file mode 100644
index 000000000..1840c1790
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ic_cam.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ic_launcher_santa.png b/santa-tracker/src/main/res/drawable-xhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..7fff7ded1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ic_weather.png b/santa-tracker/src/main/res/drawable-xhdpi/ic_weather.png
new file mode 100644
index 000000000..d239f0663
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ic_weather.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_1.png b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_1.png
new file mode 100644
index 000000000..efe3fddc4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_2.png b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_2.png
new file mode 100644
index 000000000..73712e4b1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_3.png b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_3.png
new file mode 100644
index 000000000..08d6bc493
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/instructions_shake_3.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_1.png b/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_1.png
new file mode 100644
index 000000000..72dff85e6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_2.png b/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_2.png
new file mode 100644
index 000000000..713d92f48
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/instructions_touch_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/locked.png b/santa-tracker/src/main/res/drawable-xhdpi/locked.png
new file mode 100644
index 000000000..b4a250973
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/map_infowindow_popup.9.png b/santa-tracker/src/main/res/drawable-xhdpi/map_infowindow_popup.9.png
new file mode 100644
index 000000000..401343c26
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/map_infowindow_popup.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_dancer.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_dancer.png
new file mode 100644
index 000000000..45c7a5e30
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_dancer.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_gumball.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_gumball.png
new file mode 100644
index 000000000..9f25fdfc6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_jetpack.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_jetpack.png
new file mode 100644
index 000000000..d11b5c7af
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_15.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_15.png
new file mode 100644
index 000000000..4bc41d6b2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_15.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_23.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_23.png
new file mode 100644
index 000000000..994fcc1d8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_locked_23.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_memory.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_memory.png
new file mode 100644
index 000000000..e577bfb5d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_rocket.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_rocket.png
new file mode 100644
index 000000000..26515bd9d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_rocket.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_santa.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_santa.png
new file mode 100644
index 000000000..9af5cb8d6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_video.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_video.png
new file mode 100644
index 000000000..255c10175
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_badge_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_memory.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_memory.png
new file mode 100644
index 000000000..d78e0ac51
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_memory_locked.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_memory_locked.png
new file mode 100644
index 000000000..007d4e112
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_memory_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_pin.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin.png
new file mode 100644
index 000000000..d8658f758
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_blue.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_blue.png
new file mode 100644
index 000000000..529aa8046
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_blue.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_light.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_light.png
new file mode 100644
index 000000000..4cfa122cc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_pin_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents1.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents1.png
new file mode 100644
index 000000000..d7cb62cea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents2.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents2.png
new file mode 100644
index 000000000..79a845399
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents3.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents3.png
new file mode 100644
index 000000000..c5e6a9b43
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents3.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents4.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents4.png
new file mode 100644
index 000000000..ec0132abe
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents4.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents5.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents5.png
new file mode 100644
index 000000000..53fcd7f56
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents5.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents6.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents6.png
new file mode 100644
index 000000000..104c9cfb3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents6.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents7.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents7.png
new file mode 100644
index 000000000..802367377
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents7.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents8.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents8.png
new file mode 100644
index 000000000..f6b901fbb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_santa_presents8.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_selector.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_selector.png
new file mode 100644
index 000000000..4974a712b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_selector.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/marker_selector_locked.png b/santa-tracker/src/main/res/drawable-xhdpi/marker_selector_locked.png
new file mode 100644
index 000000000..f80c89637
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/marker_selector_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/misc_pause.png b/santa-tracker/src/main/res/drawable-xhdpi/misc_pause.png
new file mode 100644
index 000000000..2dcc57994
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/misc_pause.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/misc_play.png b/santa-tracker/src/main/res/drawable-xhdpi/misc_play.png
new file mode 100644
index 000000000..ca243ab29
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/misc_play.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_left.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_left.png
new file mode 100644
index 000000000..4cb4244fc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_right.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_right.png
new file mode 100644
index 000000000..f26fa1bef
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_top.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_top.png
new file mode 100644
index 000000000..7824a66b9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_background_top.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_ball.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_ball.png
new file mode 100644
index 000000000..f11c9286b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_ball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_balloon.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_balloon.png
new file mode 100644
index 000000000..b529ce0d3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_balloon.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_beachball.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_beachball.png
new file mode 100644
index 000000000..dea80dc1f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_beachball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_candle.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_candle.png
new file mode 100644
index 000000000..d8f700fec
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_candle.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.png
new file mode 100644
index 000000000..be5d1d85b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_dark.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.png
new file mode 100644
index 000000000..c91898e4e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_blue_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.png
new file mode 100644
index 000000000..2447ecd89
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_orange.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.png
new file mode 100644
index 000000000..1cd48dc4d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_purple.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_red.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_red.png
new file mode 100644
index 000000000..3d4ae05a0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_cloak_red.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_frame.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_frame.png
new file mode 100644
index 000000000..fbbf14627
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_frame.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_globe.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_globe.png
new file mode 100644
index 000000000..b065ce334
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_globe.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_gumball.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_gumball.png
new file mode 100644
index 000000000..8a555961a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_locked.jpg b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_locked.jpg
new file mode 100644
index 000000000..61831b51e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_locked.jpg differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_penguin.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_penguin.png
new file mode 100644
index 000000000..1a9596ad3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_penguin.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_rabbit.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_rabbit.png
new file mode 100644
index 000000000..15e52e2fa
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_rabbit.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_reindeer.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_reindeer.png
new file mode 100644
index 000000000..8287fc77b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_reindeer.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_snowman.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_snowman.png
new file mode 100644
index 000000000..7ca05522e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_snowman.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_tree.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_tree.png
new file mode 100644
index 000000000..b16942e02
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_tree.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_trophy.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_trophy.png
new file mode 100644
index 000000000..5111d94d4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_card_trophy.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_left.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_left.png
new file mode 100644
index 000000000..98d47bc07
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_right.png b/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_right.png
new file mode 100644
index 000000000..3229ac8bb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mmg_pane_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png b/santa-tracker/src/main/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png
new file mode 100644
index 000000000..9237d75c2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/notification_small.png b/santa-tracker/src/main/res/drawable-xhdpi/notification_small.png
new file mode 100644
index 000000000..8d483c5c6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/notification_small.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/ornament.png b/santa-tracker/src/main/res/drawable-xhdpi/ornament.png
new file mode 100644
index 000000000..b30b205df
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/ornament.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/purple_100.png b/santa-tracker/src/main/res/drawable-xhdpi/purple_100.png
new file mode 100644
index 000000000..ede646502
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/purple_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/purple_25.png b/santa-tracker/src/main/res/drawable-xhdpi/purple_25.png
new file mode 100644
index 000000000..6dd931aff
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/purple_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/purple_50.png b/santa-tracker/src/main/res/drawable-xhdpi/purple_50.png
new file mode 100644
index 000000000..580fa1627
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/purple_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/purple_75.png b/santa-tracker/src/main/res/drawable-xhdpi/purple_75.png
new file mode 100644
index 000000000..272c93bde
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/purple_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/red_100.png b/santa-tracker/src/main/res/drawable-xhdpi/red_100.png
new file mode 100644
index 000000000..10d62e6bd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/red_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/red_25.png b/santa-tracker/src/main/res/drawable-xhdpi/red_25.png
new file mode 100644
index 000000000..92cb34633
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/red_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/red_50.png b/santa-tracker/src/main/res/drawable-xhdpi/red_50.png
new file mode 100644
index 000000000..f296fa1d8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/red_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/red_75.png b/santa-tracker/src/main/res/drawable-xhdpi/red_75.png
new file mode 100644
index 000000000..d9cbae7bf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/red_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_arm.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_arm.png
new file mode 100644
index 000000000..427a0e6f9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_arm.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_body.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_body.png
new file mode 100644
index 000000000..face2d2a7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_body.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_e.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_e.png
new file mode 100644
index 000000000..539fd2b4a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_e.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_n.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_n.png
new file mode 100644
index 000000000..0050fc281
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_n.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_ne.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_ne.png
new file mode 100644
index 000000000..422c3cc36
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_ne.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_nw.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_nw.png
new file mode 100644
index 000000000..a65e8de9b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_nw.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_s.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_s.png
new file mode 100644
index 000000000..58f1822ef
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_s.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_se.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_se.png
new file mode 100644
index 000000000..8d44232c0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_se.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_sw.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_sw.png
new file mode 100644
index 000000000..80d94525d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_sw.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santa_w.png b/santa-tracker/src/main/res/drawable-xhdpi/santa_w.png
new file mode 100644
index 000000000..98a95a06d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santa_w.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santacam.png b/santa-tracker/src/main/res/drawable-xhdpi/santacam.png
new file mode 100644
index 000000000..85428de2c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santacam.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/santatracker_logo_startup.png b/santa-tracker/src/main/res/drawable-xhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..c9adbddff
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/yellow_100.png b/santa-tracker/src/main/res/drawable-xhdpi/yellow_100.png
new file mode 100644
index 000000000..533d639d7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/yellow_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/yellow_25.png b/santa-tracker/src/main/res/drawable-xhdpi/yellow_25.png
new file mode 100644
index 000000000..dce9ed045
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/yellow_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/yellow_50.png b/santa-tracker/src/main/res/drawable-xhdpi/yellow_50.png
new file mode 100644
index 000000000..9a59a9485
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/yellow_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xhdpi/yellow_75.png b/santa-tracker/src/main/res/drawable-xhdpi/yellow_75.png
new file mode 100644
index 000000000..044e5e87b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xhdpi/yellow_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_car.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_car.png
new file mode 100644
index 000000000..abcae64b0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_car.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_jetpack.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_jetpack.png
new file mode 100644
index 000000000..8f264915e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_elf_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_gumball_tilt.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_gumball_tilt.png
new file mode 100644
index 000000000..0d219fe08
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_gumball_tilt.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_haywire_ride.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_haywire_ride.png
new file mode 100644
index 000000000..d9a53daa4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_haywire_ride.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_jingle_elves.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_jingle_elves.png
new file mode 100644
index 000000000..2e5818077
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_jingle_elves.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_memory_match.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_memory_match.png
new file mode 100644
index 000000000..8c50454d3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_memory_match.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_office_prank.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_office_prank.png
new file mode 100644
index 000000000..cf66901ed
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_office_prank.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santa_shake.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santa_shake.png
new file mode 100644
index 000000000..f5250f1ce
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santa_shake.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_back.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_back.png
new file mode 100644
index 000000000..31aa94599
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_back.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_takeoff.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_takeoff.png
new file mode 100644
index 000000000..24b13f220
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_santas_takeoff.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_snowdown.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_snowdown.png
new file mode 100644
index 000000000..82abd1ea9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_snowdown.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_track_santa.png b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_track_santa.png
new file mode 100644
index 000000000..5018cec3a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/android_game_cards_track_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_general.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_general.png
new file mode 100644
index 000000000..888022ea8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_general.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_natgeo.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_natgeo.png
new file mode 100644
index 000000000..8194be2ea
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_natgeo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_photo.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_photo.png
new file mode 100644
index 000000000..55a61d53a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_santa.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_santa.png
new file mode 100644
index 000000000..270b363b4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_video.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_video.png
new file mode 100644
index 000000000..3ffee1da3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/avatar_worldfacts.png b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_worldfacts.png
new file mode 100644
index 000000000..269adc09e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/avatar_worldfacts.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/blue_100.png b/santa-tracker/src/main/res/drawable-xxhdpi/blue_100.png
new file mode 100644
index 000000000..6beb0707d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/blue_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/blue_25.png b/santa-tracker/src/main/res/drawable-xxhdpi/blue_25.png
new file mode 100644
index 000000000..7b5fb5f2e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/blue_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/blue_50.png b/santa-tracker/src/main/res/drawable-xxhdpi/blue_50.png
new file mode 100644
index 000000000..9b25d4819
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/blue_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/blue_75.png b/santa-tracker/src/main/res/drawable-xxhdpi/blue_75.png
new file mode 100644
index 000000000..4fff55b4d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/blue_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/btn_enter_normal.png b/santa-tracker/src/main/res/drawable-xxhdpi/btn_enter_normal.png
new file mode 100644
index 000000000..0a2cc45ec
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/btn_enter_normal.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_end.png b/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_end.png
new file mode 100644
index 000000000..d32344d31
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_end.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_middle.png b/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_middle.png
new file mode 100644
index 000000000..c595abd80
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/countdown_box_middle.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar.png b/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar.png
new file mode 100644
index 000000000..06123258c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png b/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png
new file mode 100644
index 000000000..455c295ba
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/games_cancelbar_pressed.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_achievements.png b/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_achievements.png
new file mode 100644
index 000000000..136175e7f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_achievements.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_leaderboards.png b/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_leaderboards.png
new file mode 100644
index 000000000..5a39b3ce5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/games_fab_leaderboards.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.png
new file mode 100644
index 000000000..f4e575cd8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_inner.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png
new file mode 100644
index 000000000..f544146c4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_bg_main_outer_top.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.png
new file mode 100644
index 000000000..e704d0a22
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.png
new file mode 100644
index 000000000..7231b90d0
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_collected_disabled.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.png
new file mode 100644
index 000000000..06f40dc45
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_indicator_pending.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png
new file mode 100644
index 000000000..73d0101b2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_gumball_outlet.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_dialog.9.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_dialog.9.png
new file mode 100644
index 000000000..473bb2bb5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_dialog.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_elf.png b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_elf.png
new file mode 100644
index 000000000..dbf1c6b5d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/gbg_score_summary_elf.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/google_logo.png b/santa-tracker/src/main/res/drawable-xxhdpi/google_logo.png
new file mode 100644
index 000000000..312bab606
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/google_logo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/green_100.png b/santa-tracker/src/main/res/drawable-xxhdpi/green_100.png
new file mode 100644
index 000000000..e834ddf16
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/green_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/green_25.png b/santa-tracker/src/main/res/drawable-xxhdpi/green_25.png
new file mode 100644
index 000000000..e855dcbbd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/green_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/green_50.png b/santa-tracker/src/main/res/drawable-xxhdpi/green_50.png
new file mode 100644
index 000000000..fcc07e291
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/green_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/green_75.png b/santa-tracker/src/main/res/drawable-xxhdpi/green_75.png
new file mode 100644
index 000000000..23ff106d6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/green_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrival.png b/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrival.png
new file mode 100644
index 000000000..e73446097
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrival.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrow_upward_white_36dp.png b/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrow_upward_white_36dp.png
new file mode 100644
index 000000000..eceb34c77
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ic_arrow_upward_white_36dp.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ic_cam.png b/santa-tracker/src/main/res/drawable-xxhdpi/ic_cam.png
new file mode 100644
index 000000000..450c5be7e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ic_cam.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ic_launcher_santa.png b/santa-tracker/src/main/res/drawable-xxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..204839dd2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ic_weather.png b/santa-tracker/src/main/res/drawable-xxhdpi/ic_weather.png
new file mode 100644
index 000000000..babf703a8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ic_weather.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_1.png b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_1.png
new file mode 100644
index 000000000..829b5823c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_2.png b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_2.png
new file mode 100644
index 000000000..055c12827
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_3.png b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_3.png
new file mode 100644
index 000000000..694d58834
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_shake_3.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_1.png b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_1.png
new file mode 100644
index 000000000..7858e3378
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_2.png b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_2.png
new file mode 100644
index 000000000..f19c67b10
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/instructions_touch_2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/locked.png b/santa-tracker/src/main/res/drawable-xxhdpi/locked.png
new file mode 100644
index 000000000..11f9bb49c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/map_infowindow_popup.9.png b/santa-tracker/src/main/res/drawable-xxhdpi/map_infowindow_popup.9.png
new file mode 100644
index 000000000..685b5b39f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/map_infowindow_popup.9.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_dancer.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_dancer.png
new file mode 100644
index 000000000..1fff9cdb5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_dancer.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_gumball.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_gumball.png
new file mode 100644
index 000000000..4f0b0c02d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_jetpack.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_jetpack.png
new file mode 100644
index 000000000..410afe128
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_jetpack.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_15.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_15.png
new file mode 100644
index 000000000..6b211727e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_15.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_23.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_23.png
new file mode 100644
index 000000000..a03318498
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_locked_23.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_memory.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_memory.png
new file mode 100644
index 000000000..d2281d226
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_rocket.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_rocket.png
new file mode 100644
index 000000000..0290a01ca
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_rocket.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_santa.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_santa.png
new file mode 100644
index 000000000..88b6a4c29
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_video.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_video.png
new file mode 100644
index 000000000..77cb1dec3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_badge_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory.png
new file mode 100644
index 000000000..f67fb48dc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory_locked.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory_locked.png
new file mode 100644
index 000000000..0a6844aa8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_memory_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin.png
new file mode 100644
index 000000000..4d14a6060
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_blue.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_blue.png
new file mode 100644
index 000000000..d814d6263
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_blue.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_light.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_light.png
new file mode 100644
index 000000000..4fa027e92
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_pin_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents1.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents1.png
new file mode 100644
index 000000000..99e6db6ce
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents1.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents2.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents2.png
new file mode 100644
index 000000000..4481fc0db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents2.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents3.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents3.png
new file mode 100644
index 000000000..14677d6d1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents3.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents4.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents4.png
new file mode 100644
index 000000000..fc3be812f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents4.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents5.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents5.png
new file mode 100644
index 000000000..6f4f765cb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents5.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents6.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents6.png
new file mode 100644
index 000000000..e892cc4ae
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents6.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents7.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents7.png
new file mode 100644
index 000000000..89449b4ba
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents7.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents8.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents8.png
new file mode 100644
index 000000000..db73513ec
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_santa_presents8.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector.png
new file mode 100644
index 000000000..d3ba8c9b5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector_locked.png b/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector_locked.png
new file mode 100644
index 000000000..65bdc53bb
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/marker_selector_locked.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/misc_pause.png b/santa-tracker/src/main/res/drawable-xxhdpi/misc_pause.png
new file mode 100644
index 000000000..00d9c6d44
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/misc_pause.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/misc_play.png b/santa-tracker/src/main/res/drawable-xxhdpi/misc_play.png
new file mode 100644
index 000000000..2fcd4d08d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/misc_play.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_left.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_left.png
new file mode 100644
index 000000000..4028a1384
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_right.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_right.png
new file mode 100644
index 000000000..f278b232e
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_top.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_top.png
new file mode 100644
index 000000000..43513abca
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_background_top.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_ball.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_ball.png
new file mode 100644
index 000000000..9f60b37af
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_ball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_balloon.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_balloon.png
new file mode 100644
index 000000000..53b630ce2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_balloon.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_beachball.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_beachball.png
new file mode 100644
index 000000000..7f59865f9
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_beachball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_candle.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_candle.png
new file mode 100644
index 000000000..bab74b985
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_candle.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.png
new file mode 100644
index 000000000..4bc046ecd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_dark.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.png
new file mode 100644
index 000000000..edf2f84d8
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_blue_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.png
new file mode 100644
index 000000000..bec25e48c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_orange.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.png
new file mode 100644
index 000000000..2c693eab2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_purple.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.png
new file mode 100644
index 000000000..b5adfb063
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_cloak_red.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_frame.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_frame.png
new file mode 100644
index 000000000..4ea5c95bc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_frame.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_globe.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_globe.png
new file mode 100644
index 000000000..7d4633a2a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_globe.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_gumball.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_gumball.png
new file mode 100644
index 000000000..0ab6970f6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_gumball.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg
new file mode 100644
index 000000000..0ff8d4a42
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_locked.jpg differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_penguin.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_penguin.png
new file mode 100644
index 000000000..78df00ccf
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_penguin.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_rabbit.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_rabbit.png
new file mode 100644
index 000000000..4af522d9b
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_rabbit.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_reindeer.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_reindeer.png
new file mode 100644
index 000000000..ab3341bd6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_reindeer.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_snowman.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_snowman.png
new file mode 100644
index 000000000..957d05b3c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_snowman.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_tree.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_tree.png
new file mode 100644
index 000000000..555c4093f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_tree.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_trophy.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_trophy.png
new file mode 100644
index 000000000..b8ff6383c
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_card_trophy.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_left.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_left.png
new file mode 100644
index 000000000..a232d020f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_left.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_right.png b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_right.png
new file mode 100644
index 000000000..14e3f2b02
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mmg_pane_right.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png b/santa-tracker/src/main/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png
new file mode 100644
index 000000000..30c32badc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/notification_small.png b/santa-tracker/src/main/res/drawable-xxhdpi/notification_small.png
new file mode 100644
index 000000000..bffdd6b04
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/notification_small.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/ornament.png b/santa-tracker/src/main/res/drawable-xxhdpi/ornament.png
new file mode 100644
index 000000000..8020f6395
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/ornament.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/purple_100.png b/santa-tracker/src/main/res/drawable-xxhdpi/purple_100.png
new file mode 100644
index 000000000..5a1b5af74
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/purple_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/purple_25.png b/santa-tracker/src/main/res/drawable-xxhdpi/purple_25.png
new file mode 100644
index 000000000..b2ad932e6
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/purple_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/purple_50.png b/santa-tracker/src/main/res/drawable-xxhdpi/purple_50.png
new file mode 100644
index 000000000..454ee87cd
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/purple_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/purple_75.png b/santa-tracker/src/main/res/drawable-xxhdpi/purple_75.png
new file mode 100644
index 000000000..d775fcc1a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/purple_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/red_100.png b/santa-tracker/src/main/res/drawable-xxhdpi/red_100.png
new file mode 100644
index 000000000..6f74ba7ca
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/red_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/red_25.png b/santa-tracker/src/main/res/drawable-xxhdpi/red_25.png
new file mode 100644
index 000000000..1d8bede12
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/red_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/red_50.png b/santa-tracker/src/main/res/drawable-xxhdpi/red_50.png
new file mode 100644
index 000000000..d7ed111ef
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/red_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/red_75.png b/santa-tracker/src/main/res/drawable-xxhdpi/red_75.png
new file mode 100644
index 000000000..65990a870
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/red_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_arm.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_arm.png
new file mode 100644
index 000000000..1ccc56a17
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_arm.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_body.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_body.png
new file mode 100644
index 000000000..020f87d21
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_body.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_e.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_e.png
new file mode 100644
index 000000000..740d211db
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_e.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_n.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_n.png
new file mode 100644
index 000000000..d121b6ef1
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_n.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_ne.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_ne.png
new file mode 100644
index 000000000..4c000647a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_ne.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_nw.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_nw.png
new file mode 100644
index 000000000..461594cc3
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_nw.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_s.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_s.png
new file mode 100644
index 000000000..af7b64fce
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_s.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_se.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_se.png
new file mode 100644
index 000000000..770fe7010
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_se.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_sw.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_sw.png
new file mode 100644
index 000000000..c538bef1f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_sw.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santa_w.png b/santa-tracker/src/main/res/drawable-xxhdpi/santa_w.png
new file mode 100644
index 000000000..9a74b0763
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santa_w.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santacam.png b/santa-tracker/src/main/res/drawable-xxhdpi/santacam.png
new file mode 100644
index 000000000..71a28c4cc
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santacam.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/santatracker_logo_startup.png b/santa-tracker/src/main/res/drawable-xxhdpi/santatracker_logo_startup.png
new file mode 100644
index 000000000..3e6f76795
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/santatracker_logo_startup.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/yellow_100.png b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_100.png
new file mode 100644
index 000000000..c2353d0ee
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_100.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/yellow_25.png b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_25.png
new file mode 100644
index 000000000..58941797a
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_25.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/yellow_50.png b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_50.png
new file mode 100644
index 000000000..4075e7382
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_50.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxhdpi/yellow_75.png b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_75.png
new file mode 100644
index 000000000..8451727d4
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxhdpi/yellow_75.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_general.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_general.png
new file mode 100644
index 000000000..abcb7bc93
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_general.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_natgeo.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_natgeo.png
new file mode 100644
index 000000000..90f8b307d
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_natgeo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_photo.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_photo.png
new file mode 100644
index 000000000..4a83588ef
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_photo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_santa.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_santa.png
new file mode 100644
index 000000000..21c2d35e2
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_video.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_video.png
new file mode 100644
index 000000000..8d3f32869
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_video.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_worldfacts.png b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_worldfacts.png
new file mode 100644
index 000000000..17a0a4074
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/avatar_worldfacts.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_end.png b/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_end.png
new file mode 100644
index 000000000..56dd24ee7
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_end.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_middle.png b/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_middle.png
new file mode 100644
index 000000000..cf87ca8b5
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/countdown_box_middle.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/google_logo.png b/santa-tracker/src/main/res/drawable-xxxhdpi/google_logo.png
new file mode 100644
index 000000000..1c1bd48ec
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/google_logo.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/ic_arrow_upward_white_36dp.png b/santa-tracker/src/main/res/drawable-xxxhdpi/ic_arrow_upward_white_36dp.png
new file mode 100644
index 000000000..5e61c3d18
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/ic_arrow_upward_white_36dp.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/ic_launcher_santa.png b/santa-tracker/src/main/res/drawable-xxxhdpi/ic_launcher_santa.png
new file mode 100644
index 000000000..9364e085f
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/ic_launcher_santa.png differ
diff --git a/santa-tracker/src/main/res/drawable-xxxhdpi/santacam.png b/santa-tracker/src/main/res/drawable-xxxhdpi/santacam.png
new file mode 100644
index 000000000..6e626c798
Binary files /dev/null and b/santa-tracker/src/main/res/drawable-xxxhdpi/santacam.png differ
diff --git a/santa-tracker/src/main/res/drawable/bg_copyright.xml b/santa-tracker/src/main/res/drawable/bg_copyright.xml
new file mode 100644
index 000000000..0c515c1a1
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/bg_copyright.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/bg_top.xml b/santa-tracker/src/main/res/drawable/bg_top.xml
new file mode 100644
index 000000000..ee2f29e07
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/bg_top.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+ -
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/big_play_button.xml b/santa-tracker/src/main/res/drawable/big_play_button.xml
new file mode 100644
index 000000000..84f90c0c5
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/big_play_button.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/button_achievements.xml b/santa-tracker/src/main/res/drawable/button_achievements.xml
new file mode 100644
index 000000000..32ce78fa2
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/button_achievements.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/button_leaderboards.xml b/santa-tracker/src/main/res/drawable/button_leaderboards.xml
new file mode 100644
index 000000000..5b5ab200c
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/button_leaderboards.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/button_selector.xml b/santa-tracker/src/main/res/drawable/button_selector.xml
new file mode 100644
index 000000000..b0b1a3909
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/button_selector.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/cancelbar_pressed.xml b/santa-tracker/src/main/res/drawable/cancelbar_pressed.xml
new file mode 100644
index 000000000..3d5a6870b
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/cancelbar_pressed.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/card_ripple.xml b/santa-tracker/src/main/res/drawable/card_ripple.xml
new file mode 100644
index 000000000..dcf42f94f
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/card_ripple.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+ -
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/marker_selector_locked_states.xml b/santa-tracker/src/main/res/drawable/marker_selector_locked_states.xml
new file mode 100644
index 000000000..fc7e4b627
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/marker_selector_locked_states.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/marker_selector_states.xml b/santa-tracker/src/main/res/drawable/marker_selector_states.xml
new file mode 100644
index 000000000..5bb8ae46e
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/marker_selector_states.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/play_again_button.xml b/santa-tracker/src/main/res/drawable/play_again_button.xml
new file mode 100644
index 000000000..1c1075a68
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/play_again_button.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/rounded_rect_ice.xml b/santa-tracker/src/main/res/drawable/rounded_rect_ice.xml
new file mode 100644
index 000000000..a9887429f
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/rounded_rect_ice.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/score_background_gingerbread.xml b/santa-tracker/src/main/res/drawable/score_background_gingerbread.xml
new file mode 100644
index 000000000..60bbfbee6
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/score_background_gingerbread.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/showcase_button.xml b/santa-tracker/src/main/res/drawable/showcase_button.xml
new file mode 100644
index 000000000..884cf9e59
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/showcase_button.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/showcase_button_normal.xml b/santa-tracker/src/main/res/drawable/showcase_button_normal.xml
new file mode 100644
index 000000000..bf8e2412b
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/showcase_button_normal.xml
@@ -0,0 +1,17 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/showcase_button_pressed.xml b/santa-tracker/src/main/res/drawable/showcase_button_pressed.xml
new file mode 100644
index 000000000..405371375
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/showcase_button_pressed.xml
@@ -0,0 +1,17 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/simple_pressed.xml b/santa-tracker/src/main/res/drawable/simple_pressed.xml
new file mode 100644
index 000000000..ed1d75420
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/simple_pressed.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/drawable/stream_separator.xml b/santa-tracker/src/main/res/drawable/stream_separator.xml
new file mode 100644
index 000000000..422214857
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/stream_separator.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+ -
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/drawable/tv_tracker_card_selector.xml b/santa-tracker/src/main/res/drawable/tv_tracker_card_selector.xml
new file mode 100644
index 000000000..7b0da8fdf
--- /dev/null
+++ b/santa-tracker/src/main/res/drawable/tv_tracker_card_selector.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout-sw600dp/activity_memory.xml b/santa-tracker/src/main/res/layout-sw600dp/activity_memory.xml
new file mode 100644
index 000000000..8c88e8d8c
--- /dev/null
+++ b/santa-tracker/src/main/res/layout-sw600dp/activity_memory.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout-sw600dp/fragment_gumball.xml b/santa-tracker/src/main/res/layout-sw600dp/fragment_gumball.xml
new file mode 100644
index 000000000..db37109e8
--- /dev/null
+++ b/santa-tracker/src/main/res/layout-sw600dp/fragment_gumball.xml
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout-sw720dp/countdown.xml b/santa-tracker/src/main/res/layout-sw720dp/countdown.xml
new file mode 100644
index 000000000..5777eae79
--- /dev/null
+++ b/santa-tracker/src/main/res/layout-sw720dp/countdown.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout-sw720dp/include_score_summary.xml b/santa-tracker/src/main/res/layout-sw720dp/include_score_summary.xml
new file mode 100644
index 000000000..57af0c1a4
--- /dev/null
+++ b/santa-tracker/src/main/res/layout-sw720dp/include_score_summary.xml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout-w720dp/activity_map.xml b/santa-tracker/src/main/res/layout-w720dp/activity_map.xml
new file mode 100644
index 000000000..0a964f3e2
--- /dev/null
+++ b/santa-tracker/src/main/res/layout-w720dp/activity_map.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/activity_gumball.xml b/santa-tracker/src/main/res/layout/activity_gumball.xml
new file mode 100644
index 000000000..8c88e8d8c
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_gumball.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/activity_jetpack.xml b/santa-tracker/src/main/res/layout/activity_jetpack.xml
new file mode 100644
index 000000000..8ec95a538
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_jetpack.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/activity_map.xml b/santa-tracker/src/main/res/layout/activity_map.xml
new file mode 100644
index 000000000..8f711d44c
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_map.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/activity_map_tv.xml b/santa-tracker/src/main/res/layout/activity_map_tv.xml
new file mode 100644
index 000000000..bff15e063
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_map_tv.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/activity_memory.xml b/santa-tracker/src/main/res/layout/activity_memory.xml
new file mode 100644
index 000000000..8c88e8d8c
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_memory.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/activity_startup.xml b/santa-tracker/src/main/res/layout/activity_startup.xml
new file mode 100644
index 000000000..0c2963739
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/activity_startup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/countdown.xml b/santa-tracker/src/main/res/layout/countdown.xml
new file mode 100644
index 000000000..640b20ffe
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/countdown.xml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/fragment_gumball.xml b/santa-tracker/src/main/res/layout/fragment_gumball.xml
new file mode 100644
index 000000000..44cf4253f
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/fragment_gumball.xml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/fragment_memory_match.xml b/santa-tracker/src/main/res/layout/fragment_memory_match.xml
new file mode 100644
index 000000000..d32ac9905
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/fragment_memory_match.xml
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/include_instruction_image.xml b/santa-tracker/src/main/res/layout/include_instruction_image.xml
new file mode 100644
index 000000000..142ddb9b5
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/include_instruction_image.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/include_score_summary.xml b/santa-tracker/src/main/res/layout/include_score_summary.xml
new file mode 100644
index 000000000..e10e4e3bc
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/include_score_summary.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/infowindow.xml b/santa-tracker/src/main/res/layout/infowindow.xml
new file mode 100644
index 000000000..2b0a0d5a6
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/infowindow.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/item_card_dashboard.xml b/santa-tracker/src/main/res/layout/item_card_dashboard.xml
new file mode 100644
index 000000000..5ebaca041
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_dashboard.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_card_destination.xml b/santa-tracker/src/main/res/layout/item_card_destination.xml
new file mode 100644
index 000000000..4d504fd72
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_destination.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_card_factoid.xml b/santa-tracker/src/main/res/layout/item_card_factoid.xml
new file mode 100644
index 000000000..3bc1684b6
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_factoid.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_card_movie.xml b/santa-tracker/src/main/res/layout/item_card_movie.xml
new file mode 100644
index 000000000..6bdc1c4a0
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_movie.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_card_photo.xml b/santa-tracker/src/main/res/layout/item_card_photo.xml
new file mode 100644
index 000000000..13dc81aea
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_photo.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_card_update.xml b/santa-tracker/src/main/res/layout/item_card_update.xml
new file mode 100644
index 000000000..c04152be4
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_card_update.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/item_memory_card.xml b/santa-tracker/src/main/res/layout/item_memory_card.xml
new file mode 100644
index 000000000..525a3bd87
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/item_memory_card.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/layout_countdown_2015.xml b/santa-tracker/src/main/res/layout/layout_countdown_2015.xml
new file mode 100644
index 000000000..0a5d75687
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/layout_countdown_2015.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/layout_daydream.xml b/santa-tracker/src/main/res/layout/layout_daydream.xml
new file mode 100644
index 000000000..7c6034dba
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/layout_daydream.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/layout_game_fragment.xml b/santa-tracker/src/main/res/layout/layout_game_fragment.xml
new file mode 100644
index 000000000..0c3709201
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/layout_game_fragment.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/santa-tracker/src/main/res/layout/layout_startup.xml b/santa-tracker/src/main/res/layout/layout_startup.xml
new file mode 100644
index 000000000..ebc7c8023
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/layout_startup.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/santa-tracker/src/main/res/layout/layout_startup_2015.xml b/santa-tracker/src/main/res/layout/layout_startup_2015.xml
new file mode 100644
index 000000000..735f3ca73
--- /dev/null
+++ b/santa-tracker/src/main/res/layout/layout_startup_2015.xml
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+