diff --git a/CCL/build.gradle b/CCL/build.gradle new file mode 100644 index 000000000..d25fde4ed --- /dev/null +++ b/CCL/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion rootProject.ext.tools + publishNonDefault true + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 23 + versionCode 10 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + + +dependencies { + compile rootProject.ext.supportV4 + compile rootProject.ext.appCompat + compile rootProject.ext.mediaRouter + + compile rootProject.ext.playServicesBase + compile rootProject.ext.playServicesBasement + compile rootProject.ext.playServicesCast +} diff --git a/CCL/src/main/AndroidManifest.xml b/CCL/src/main/AndroidManifest.xml new file mode 100644 index 000000000..d717cc38e --- /dev/null +++ b/CCL/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/BaseCastManager.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/BaseCastManager.java new file mode 100644 index 000000000..788725c38 --- /dev/null +++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/BaseCastManager.java @@ -0,0 +1,1236 @@ +/* + * 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; + +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.ApplicationMetadata; +import com.google.android.gms.cast.Cast; +import com.google.android.gms.cast.Cast.ApplicationConnectionResult; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastMediaControlIntent; +import com.google.android.gms.cast.CastStatusCodes; +import com.google.android.gms.cast.LaunchOptions; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.libraries.cast.companionlibrary.R; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.BaseCastConsumer; +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.cast.reconnection.ReconnectionService; +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.TargetApi; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.media.RemoteControlClient; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.support.annotation.IntDef; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.MediaRouteActionProvider; +import android.support.v7.app.MediaRouteButton; +import android.support.v7.app.MediaRouteDialogFactory; +import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; +import android.support.v7.media.MediaRouter.RouteInfo; +import android.view.Menu; +import android.view.MenuItem; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * An abstract class that manages connectivity to a cast device. Subclasses are expected to extend + * the functionality of this class based on their purpose. + */ +public abstract class BaseCastManager + implements ConnectionCallbacks, OnConnectionFailedListener, OnFailedListener { + + private static final String TAG = LogUtils.makeLogTag(BaseCastManager.class); + + public static final int RECONNECTION_STATUS_STARTED = 1; + public static final int RECONNECTION_STATUS_IN_PROGRESS = 2; + public static final int RECONNECTION_STATUS_FINALIZED = 3; + public static final int RECONNECTION_STATUS_INACTIVE = 4; + + public static final int FEATURE_DEBUGGING = 1; + public static final int FEATURE_LOCKSCREEN = 1 << 1; + public static final int FEATURE_NOTIFICATION = 1 << 2; + public static final int FEATURE_WIFI_RECONNECT = 1 << 3; + public static final int FEATURE_CAPTIONS_PREFERENCE = 1 << 4; + public static final int FEATURE_AUTO_RECONNECT = 1 << 5; + + public static final String PREFS_KEY_SESSION_ID = "session-id"; + public static final String PREFS_KEY_SSID = "ssid"; + public static final String PREFS_KEY_MEDIA_END = "media-end"; + public static final String PREFS_KEY_APPLICATION_ID = "application-id"; + public static final String PREFS_KEY_CAST_ACTIVITY_NAME = "cast-activity-name"; + public static final String PREFS_KEY_CAST_CUSTOM_DATA_NAMESPACE = "cast-custom-data-namespace"; + public static final String PREFS_KEY_ROUTE_ID = "route-id"; + + public static final int CLEAR_ALL = 0; + public static final int CLEAR_ROUTE = 1; + public static final int CLEAR_WIFI = 1 << 1; + public static final int CLEAR_SESSION = 1 << 2; + public static final int CLEAR_MEDIA_END = 1 << 3; + + public static final int DISCONNECT_REASON_OTHER = 0; + public static final int DISCONNECT_REASON_CONNECTIVITY = 1; + public static final int DISCONNECT_REASON_APP_NOT_RUNNING = 2; + public static final int DISCONNECT_REASON_EXPLICIT = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({DISCONNECT_REASON_OTHER, DISCONNECT_REASON_CONNECTIVITY, + DISCONNECT_REASON_APP_NOT_RUNNING, DISCONNECT_REASON_EXPLICIT}) + public @interface DISCONNECT_REASON {} + + public static final int NO_APPLICATION_ERROR = 0; + + public static final int NO_STATUS_CODE = -1; + private static final int SESSION_RECOVERY_TIMEOUT_S = 10; + private static final int WHAT_UI_VISIBLE = 0; + private static final int WHAT_UI_HIDDEN = 1; + private static final int UI_VISIBILITY_DELAY_MS = 300; + + private static String sCclVersion; + + protected Context mContext; + protected MediaRouter mMediaRouter; + protected MediaRouteSelector mMediaRouteSelector; + protected CastMediaRouterCallback mMediaRouterCallback; + protected CastDevice mSelectedCastDevice; + protected String mDeviceName; + protected PreferenceAccessor mPreferenceAccessor; + + private final Set mBaseCastConsumers = new CopyOnWriteArraySet<>(); + private boolean mDestroyOnDisconnect = false; + protected String mApplicationId; + protected int mReconnectionStatus = RECONNECTION_STATUS_INACTIVE; + protected int mVisibilityCounter; + protected boolean mUiVisible; + protected GoogleApiClient mApiClient; + protected AsyncTask mReconnectionTask; + protected int mCapabilities; + protected boolean mConnectionSuspended; + protected String mSessionId; + private Handler mUiVisibilityHandler; + private RouteInfo mRouteInfo; + protected int mApplicationErrorCode = NO_APPLICATION_ERROR; + protected LaunchOptions mLaunchOptions; + + protected BaseCastManager() { + } + + /** + * Since application lifecycle callbacks are managed by subclasses, this abstract method needs + * to be implemented by each subclass independently. + * + * @param device The Cast receiver device returned from the MediaRouteProvider. Should not be + * {@code null}. + */ + protected abstract Cast.CastOptions.Builder getCastOptionBuilder(CastDevice device); + + /** + * Subclasses can decide how the Cast Controller Dialog should be built. If this returns + * null, the default dialog will be shown. + */ + protected abstract MediaRouteDialogFactory getMediaRouteDialogFactory(); + + /** + * Subclasses should implement this to react appropriately to the successful launch of their + * application. This is called when the application is successfully launched. + */ + protected abstract void onApplicationConnected(ApplicationMetadata applicationMetadata, + String applicationStatus, String sessionId, boolean wasLaunched); + + /** + * Called when the launch of application has failed. Subclasses need to handle this by doing + * appropriate clean up. + */ + protected abstract void onApplicationConnectionFailed(int statusCode); + + /** + * Called when the attempt to stop application has failed. + */ + protected abstract void onApplicationStopFailed(int statusCode); + + /** + * Called when a Cast device is unselected (i.e. disconnected). Most of the logic is handled by + * the {@link BaseCastManager} but each subclass may have some additional logic that can be + * done, e.g. detaching data or media channels that they may have set up. + */ + protected void onDeviceUnselected() { + // no-op implementation + } + + protected BaseCastManager(Context context, String applicationId) { + sCclVersion = context.getString(R.string.ccl_version); + LOGD(TAG, "BaseCastManager is instantiated\nVersion: " + sCclVersion + + "\nApplication ID: " + applicationId); + mContext = context.getApplicationContext(); + mPreferenceAccessor = new PreferenceAccessor(mContext); + mUiVisibilityHandler = new Handler(new UpdateUiVisibilityHandlerCallback()); + mApplicationId = applicationId; + mLaunchOptions = new LaunchOptions.Builder().setRelaunchIfRunning(false).build(); + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_APPLICATION_ID, applicationId); + + mMediaRouter = MediaRouter.getInstance(mContext); + mMediaRouteSelector = new MediaRouteSelector.Builder().addControlCategory( + CastMediaControlIntent.categoryForCast(mApplicationId)).build(); + + mMediaRouterCallback = new CastMediaRouterCallback(this); + mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); + } + + /** + * Called when a {@link CastDevice} is extracted from the {@link RouteInfo}. This is where all + * the fun starts! + */ + public final void onDeviceSelected(CastDevice device) { + if (device == null) { + disconnectDevice(mDestroyOnDisconnect, true, false); + } else { + setDevice(device); + } + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onDeviceSelected(device); + } + } + + /** + * This is called from + * {@link com.google.android.libraries.cast.companionlibrary.cast.CastMediaRouterCallback} to + * signal the change in presence of cast devices on network. + * + * @param castDevicePresent Indicates where a cast device is present, true, or not, + * false. + */ + public final void onCastAvailabilityChanged(boolean castDevicePresent) { + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onCastAvailabilityChanged(castDevicePresent); + } + } + + /** + * Disconnects from the connected device. + * + * @param stopAppOnExit If {@code true}, the application running on the cast device will be + * stopped when disconnected. + * @param clearPersistedConnectionData If {@code true}, the persisted connection information + * will be cleared as part of this call. + * @param setDefaultRoute If {@code true}, after disconnection, the selected route will be set + * to the Default Route. + */ + public final void disconnectDevice(boolean stopAppOnExit, boolean clearPersistedConnectionData, + boolean setDefaultRoute) { + LOGD(TAG, "disconnectDevice(" + clearPersistedConnectionData + "," + setDefaultRoute + ")"); + if (mSelectedCastDevice == null) { + return; + } + mSelectedCastDevice = null; + mDeviceName = null; + + String message = "disconnectDevice() Disconnect Reason: "; + int reason; + if (mConnectionSuspended) { + message += "Connectivity lost"; + reason = DISCONNECT_REASON_CONNECTIVITY; + } else { + switch (mApplicationErrorCode) { + case CastStatusCodes.APPLICATION_NOT_RUNNING: + message += "App was taken over or not available anymore"; + reason = DISCONNECT_REASON_APP_NOT_RUNNING; + break; + case NO_APPLICATION_ERROR: + message += "Intentional disconnect"; + reason = DISCONNECT_REASON_EXPLICIT; + break; + default: + message += "Other"; + reason = DISCONNECT_REASON_OTHER; + } + } + LOGD(TAG, message); + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onDisconnectionReason(reason); + } + + LOGD(TAG, "mConnectionSuspended: " + mConnectionSuspended); + if (!mConnectionSuspended && clearPersistedConnectionData) { + clearPersistedConnectionInfo(CLEAR_ALL); + stopReconnectionService(); + } + try { + if ((isConnected() || isConnecting()) && stopAppOnExit) { + LOGD(TAG, "Calling stopApplication"); + stopApplication(); + } + } catch (NoConnectionException | TransientNetworkDisconnectionException e) { + LOGE(TAG, "Failed to stop the application after disconnecting route", e); + } + onDeviceUnselected(); + if (mApiClient != null) { + // the following check is currently required, without including a check for + // isConnecting() due to a bug in the current play services library and will be removed + // when that bug is addressed; calling disconnect() while we are in "connecting" state + // will throw an exception + if (mApiClient.isConnected()) { + LOGD(TAG, "Trying to disconnect"); + mApiClient.disconnect(); + } + if ((mMediaRouter != null) && setDefaultRoute) { + LOGD(TAG, "disconnectDevice(): Setting route to default"); + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + mApiClient = null; + } + mSessionId = null; + onDisconnected(stopAppOnExit, clearPersistedConnectionData, setDefaultRoute); + } + + /** + * Returns {@code true} if and only if the selected cast device is on the local network. + * + * @throws CastException if no cast device has been selected. + */ + public final boolean isDeviceOnLocalNetwork() throws CastException { + if (mSelectedCastDevice == null) { + throw new CastException("No cast device has yet been selected"); + } + return mSelectedCastDevice.isOnLocalNetwork(); + } + + private void setDevice(CastDevice device) { + mSelectedCastDevice = device; + mDeviceName = mSelectedCastDevice.getFriendlyName(); + + if (mApiClient == null) { + LOGD(TAG, "acquiring a connection to Google Play services for " + mSelectedCastDevice); + Cast.CastOptions.Builder apiOptionsBuilder = getCastOptionBuilder(mSelectedCastDevice); + mApiClient = new GoogleApiClient.Builder(mContext) + .addApi(Cast.API, apiOptionsBuilder.build()) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .build(); + mApiClient.connect(); + } else if (!mApiClient.isConnected() && !mApiClient.isConnecting()) { + mApiClient.connect(); + } + } + + /** + * Called as soon as a non-default {@link RouteInfo} is discovered. The main usage for this is + * to provide a hint to clients that the cast button is going to become visible/available soon. + * A client, for example, can use this to show a quick help screen to educate the user on the + * cast concept and the usage of the cast button. + */ + public final void onCastDeviceDetected(RouteInfo info) { + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onCastDeviceDetected(info); + } + } + + /** + * Adds and wires up the Media Router cast button. It returns a reference to the Media Router + * menu item if the caller needs such reference. It is assumed that the enclosing + * {@link android.app.Activity} inherits (directly or indirectly) from + * {@link android.support.v7.app.AppCompatActivity}. + * + * @param menu Menu reference + * @param menuResourceId The resource id of the cast button in the xml menu descriptor file + */ + public final MenuItem addMediaRouterButton(Menu menu, int menuResourceId) { + MenuItem mediaRouteMenuItem = menu.findItem(menuResourceId); + MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) + MenuItemCompat.getActionProvider(mediaRouteMenuItem); + mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector); + if (getMediaRouteDialogFactory() != null) { + mediaRouteActionProvider.setDialogFactory(getMediaRouteDialogFactory()); + } + return mediaRouteMenuItem; + } + + /** + * Adds and wires up the {@link android.support.v7.app.MediaRouteButton} instance that is passed + * as an argument. This requires that + * + */ + public final void addMediaRouterButton(MediaRouteButton button) { + button.setRouteSelector(mMediaRouteSelector); + if (getMediaRouteDialogFactory() != null) { + button.setDialogFactory(getMediaRouteDialogFactory()); + } + } + + /** + * Calling this method signals the library that an activity page is made visible. In common + * cases, this should be called in the "onResume()" method of each activity of the application. + * The library keeps a counter and when at least one page of the application becomes visible, + * the {@link #onUiVisibilityChanged(boolean)} method is called. + */ + public final synchronized void incrementUiCounter() { + mVisibilityCounter++; + if (!mUiVisible) { + mUiVisible = true; + mUiVisibilityHandler.removeMessages(WHAT_UI_HIDDEN); + mUiVisibilityHandler.sendEmptyMessageDelayed(WHAT_UI_VISIBLE, UI_VISIBILITY_DELAY_MS); + } + if (mVisibilityCounter == 0) { + LOGD(TAG, "UI is no longer visible"); + } else { + LOGD(TAG, "UI is visible"); + } + } + + /** + * Calling this method signals the library that an activity page is made invisible. In common + * cases, this should be called in the "onPause()" method of each activity of the application. + * The library keeps a counter and when all pages of the application become invisible, the + * {@link #onUiVisibilityChanged(boolean)} method is called. + */ + public final synchronized void decrementUiCounter() { + if (--mVisibilityCounter == 0) { + LOGD(TAG, "UI is no longer visible"); + if (mUiVisible) { + mUiVisible = false; + mUiVisibilityHandler.removeMessages(WHAT_UI_VISIBLE); + mUiVisibilityHandler.sendEmptyMessageDelayed(WHAT_UI_HIDDEN, + UI_VISIBILITY_DELAY_MS); + } + } else { + LOGD(TAG, "UI is visible"); + } + } + + /** + * This is called when UI visibility of the client has changed + * + * @param visible The updated visibility status + */ + protected void onUiVisibilityChanged(boolean visible) { + if (visible) { + if (mMediaRouter != null && mMediaRouterCallback != null) { + LOGD(TAG, "onUiVisibilityChanged() addCallback called"); + startCastDiscovery(); + if (isFeatureEnabled(FEATURE_AUTO_RECONNECT)) { + reconnectSessionIfPossible(); + } + } + } else { + if (mMediaRouter != null) { + LOGD(TAG, "onUiVisibilityChanged() removeCallback called"); + stopCastDiscovery(); + } + } + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onUiVisibilityChanged(visible); + } + } + + /** + * Starts the discovery of cast devices by registering a {@link android.support.v7.media + * .MediaRouter.Callback} + */ + public final void startCastDiscovery() { + mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); + } + + /** + * Stops the process of cast discovery by removing the registered + * {@link android.support.v7.media.MediaRouter.Callback} + */ + public final void stopCastDiscovery() { + mMediaRouter.removeCallback(mMediaRouterCallback); + } + + /** + * 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) { + return Utils.checkGooglePlayServices(activity); + } + + /** + * can be used to find out if the application is connected to the service or not. + * + * @return true if connected, false otherwise. + */ + public final boolean isConnected() { + return (mApiClient != null) && mApiClient.isConnected(); + } + + /** + * Returns true only if application is connecting to the Cast service. + */ + public final boolean isConnecting() { + return (mApiClient != null) && mApiClient.isConnecting(); + } + + /** + * Disconnects from the cast device and stops the application on the cast device. + */ + public final void disconnect() { + if (isConnected() || isConnecting()) { + disconnectDevice(mDestroyOnDisconnect, true, true); + } + } + + /** + * Returns the assigned human-readable name of the device, or null if no device is + * connected. + */ + public final String getDeviceName() { + return mDeviceName; + } + + /** + * Sets a flag to control whether disconnection form a cast device should result in stopping + * the running application or not. If true is passed, then application will be + * stopped. Default behavior is not to stop the app. + */ + public final void setStopOnDisconnect(boolean stopOnExit) { + mDestroyOnDisconnect = stopOnExit; + } + + /** + * Returns the {@link MediaRouteSelector} object. + */ + public final MediaRouteSelector getMediaRouteSelector() { + return mMediaRouteSelector; + } + + /** + * Returns the {@link android.support.v7.media.MediaRouter.RouteInfo} corresponding to the + * selected route. + */ + public final RouteInfo getRouteInfo() { + return mRouteInfo; + } + + /** + * Sets the {@link android.support.v7.media.MediaRouter.RouteInfo} corresponding to the + * selected route. + */ + public final void setRouteInfo(RouteInfo routeInfo) { + mRouteInfo = routeInfo; + } + + /** + * Turns on configurable features in the library. All the supported features are turned off by + * default and clients, prior to using them, need to turn them on; it is best to do this + * immediately after initialization of the library. Bitwise OR combination of features should be + * passed in if multiple features are needed + *

+ * Current set of configurable features are: + *

+ */ + public final void enableFeatures(int capabilities) { + mCapabilities = capabilities; + onFeaturesUpdated(mCapabilities); + } + + /* + * Returns true if and only if the feature is turned on + */ + public final boolean isFeatureEnabled(int feature) { + return (feature & mCapabilities) == feature; + } + + /** + * Allow subclasses to be notified of changes to capabilities if they want to. + */ + protected void onFeaturesUpdated(int capabilities) { + } + + /** + * Sets the device (system) volume. + * + * @param volume Should be a value between 0 and 1, inclusive. + * @throws CastException + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final void setDeviceVolume(double volume) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + try { + Cast.CastApi.setVolume(mApiClient, volume); + } catch (IOException e) { + throw new CastException("Failed to set volume", e); + } catch (IllegalStateException e) { + throw new NoConnectionException("setDeviceVolume()", e); + } + } + + /** + * Gets the remote's system volume, a number between 0 and 1, inclusive. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final double getDeviceVolume() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + try { + return Cast.CastApi.getVolume(mApiClient); + } catch (IllegalStateException e) { + throw new NoConnectionException("getDeviceVolume()", e); + } + } + + /** + * Increments (or decrements) the device volume by the given amount. + * + * @throws CastException + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final void adjustDeviceVolume(double delta) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + double vol = getDeviceVolume(); + if (vol >= 0) { + setDeviceVolume(vol + delta); + } + } + + /** + * Returns true if remote device is muted. It internally determines if this should + * be done for stream or device volume. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final boolean isDeviceMute() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + try { + return Cast.CastApi.isMute(mApiClient); + } catch (IllegalStateException e) { + throw new NoConnectionException("isDeviceMute()", e); + } + } + + /** + * Mutes or un-mutes the device volume. + * + * @throws CastException + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final void setDeviceMute(boolean mute) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + try { + Cast.CastApi.setMute(mApiClient, mute); + } catch (IOException e) { + throw new CastException("setDeviceMute", e); + } catch (IllegalStateException e) { + throw new NoConnectionException("setDeviceMute()", e); + } + } + + /** + * Returns the current reconnection status + */ + public final int getReconnectionStatus() { + return mReconnectionStatus; + } + + /** + * Sets the reconnection status + */ + public final void setReconnectionStatus(int status) { + if (mReconnectionStatus != status) { + mReconnectionStatus = status; + onReconnectionStatusChanged(mReconnectionStatus); + } + } + + private void onReconnectionStatusChanged(int status) { + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onReconnectionStatusChanged(status); + } + } + + /** + * Returns true if there is enough persisted information to attempt a session + * recovery. For this to return true, there needs to be a persisted session ID and + * a route ID from the last successful launch. + */ + protected final boolean canConsiderSessionRecovery() { + return canConsiderSessionRecovery(null); + } + + /** + * Returns true if there is enough persisted information to attempt a session + * recovery. For this to return true, there needs to be persisted session ID and + * route ID from the last successful launch. In addition, if ssidName is non-null, + * then an additional check is also performed to make sure the persisted wifi name is the same + * as the ssidName + */ + public final boolean canConsiderSessionRecovery(String ssidName) { + String sessionId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_SESSION_ID); + String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID); + String ssid = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_SSID); + if (sessionId == null || routeId == null) { + return false; + } + if (ssidName != null && (ssid == null || (!ssid.equals(ssidName)))) { + return false; + } + LOGD(TAG, "Found session info in the preferences, so proceed with an " + + "attempt to reconnect if possible"); + return true; + } + + private void reconnectSessionIfPossibleInternal(RouteInfo theRoute) { + if (isConnected()) { + return; + } + String sessionId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_SESSION_ID); + String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID); + LOGD(TAG, "reconnectSessionIfPossible() Retrieved from preferences: " + "sessionId=" + + sessionId + ", routeId=" + routeId); + if (sessionId == null || routeId == null) { + return; + } + setReconnectionStatus(RECONNECTION_STATUS_IN_PROGRESS); + CastDevice device = CastDevice.getFromBundle(theRoute.getExtras()); + + if (device != null) { + LOGD(TAG, "trying to acquire Cast Client for " + device); + onDeviceSelected(device); + } + } + + /* + * Cancels the task responsible for recovery of prior sessions, is used internally. + */ + public final void cancelReconnectionTask() { + LOGD(TAG, "cancelling reconnection task"); + if (mReconnectionTask != null && !mReconnectionTask.isCancelled()) { + mReconnectionTask.cancel(true); + } + } + + /** + * This method tries to automatically re-establish re-establish connection to a session if + * + * Under these conditions, a best-effort attempt will be made to continue with the same + * session. This attempt will go on for {@code SESSION_RECOVERY_TIMEOUT} seconds. + */ + public final void reconnectSessionIfPossible() { + reconnectSessionIfPossible(SESSION_RECOVERY_TIMEOUT_S); + } + + /** + * This method tries to automatically re-establish connection to a session if + * + * Under these conditions, a best-effort attempt will be made to continue with the same + * session. This attempt will go on for timeoutInSeconds seconds. + */ + public final void reconnectSessionIfPossible(final int timeoutInSeconds) { + reconnectSessionIfPossible(timeoutInSeconds, null); + } + + /** + * This method tries to automatically re-establish connection to a session if + * + * Under these conditions, a best-effort attempt will be made to continue with the same + * session. + * This attempt will go on for timeoutInSeconds seconds. + * + * @param timeoutInSeconds the length of time, in seconds, to attempt reconnection before giving + * up + * @param ssidName The name of Wifi SSID + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public void reconnectSessionIfPossible(final int timeoutInSeconds, String ssidName) { + LOGD(TAG, String.format("reconnectSessionIfPossible(%d, %s)", timeoutInSeconds, ssidName)); + if (isConnected()) { + return; + } + String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID); + if (canConsiderSessionRecovery(ssidName)) { + List routes = mMediaRouter.getRoutes(); + RouteInfo theRoute = null; + if (routes != null) { + for (RouteInfo route : routes) { + if (route.getId().equals(routeId)) { + theRoute = route; + break; + } + } + } + if (theRoute != null) { + // route has already been discovered, so lets just get the device + reconnectSessionIfPossibleInternal(theRoute); + } else { + // we set a flag so if the route is discovered within a short period, we let + // onRouteAdded callback of CastMediaRouterCallback take care of that + setReconnectionStatus(RECONNECTION_STATUS_STARTED); + } + + // cancel any prior reconnection task + if (mReconnectionTask != null && !mReconnectionTask.isCancelled()) { + mReconnectionTask.cancel(true); + } + + // we may need to reconnect to an existing session + mReconnectionTask = new AsyncTask() { + + @Override + protected Boolean doInBackground(Void... params) { + for (int i = 0; i < timeoutInSeconds; i++) { + LOGD(TAG, "Reconnection: Attempt " + (i + 1)); + if (isCancelled()) { + return true; + } + try { + if (isConnected()) { + cancel(true); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } + } + return false; + } + + @Override + protected void onPostExecute(Boolean result) { + if (result == null || !result) { + LOGD(TAG, "Couldn't reconnect, dropping connection"); + setReconnectionStatus(RECONNECTION_STATUS_INACTIVE); + onDeviceSelected(null); + } + } + + }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mReconnectionTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + mReconnectionTask.execute(); + } + } + } + + /** + * This is called by the library when a connection is re-established after a transient + * disconnect. Note: this is not called by SDK. + */ + public void onConnectivityRecovered() { + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onConnectivityRecovered(); + } + } + + /* + * (non-Javadoc) + * @see com.google.android.gms.GoogleApiClient.ConnectionCallbacks#onConnected + * (android.os.Bundle) + */ + @Override + public final void onConnected(Bundle hint) { + LOGD(TAG, "onConnected() reached with prior suspension: " + mConnectionSuspended); + if (mConnectionSuspended) { + mConnectionSuspended = false; + if (hint != null && hint.getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) { + // the same app is not running any more + LOGD(TAG, "onConnected(): App no longer running, so disconnecting"); + disconnect(); + } else { + onConnectivityRecovered(); + } + return; + } + if (!isConnected()) { + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + setReconnectionStatus(RECONNECTION_STATUS_INACTIVE); + } + return; + } + try { + if (isFeatureEnabled(FEATURE_WIFI_RECONNECT)) { + String ssid = Utils.getWifiSsid(mContext); + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SSID, ssid); + } + Cast.CastApi.requestStatus(mApiClient); + launchApp(); + + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onConnected(); + } + + } catch (IOException | IllegalStateException e) { + LOGE(TAG, "requestStatus()", e); + } + + } + + /* + * Note: this is not called by the SDK anymore but this library calls this in the appropriate + * time. + */ + protected void onDisconnected(boolean stopAppOnExit, boolean clearPersistedConnectionData, + boolean setDefaultRoute) { + LOGD(TAG, "onDisconnected() reached"); + mDeviceName = null; + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onDisconnected(); + } + } + + /* + * (non-Javadoc) + * @see com.google.android.gms.GoogleApiClient.OnConnectionFailedListener# + * onConnectionFailed(com.google.android.gms.common.ConnectionResult) + */ + @Override + public void onConnectionFailed(ConnectionResult result) { + LOGD(TAG, "onConnectionFailed() reached, error code: " + result.getErrorCode() + + ", reason: " + result.toString()); + disconnectDevice(mDestroyOnDisconnect, false /* clearPersistentConnectionData */, + false /* setDefaultRoute */); + mConnectionSuspended = false; + if (mMediaRouter != null) { + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onConnectionFailed(result); + } + + if (result != null) { + PendingIntent pendingIntent = result.getResolution(); + if (pendingIntent != null) { + try { + pendingIntent.send(); + } catch (PendingIntent.CanceledException e) { + LOGE(TAG, "Failed to show recovery from the recoverable error", e); + } + } + } + } + + @Override + public void onConnectionSuspended(int cause) { + mConnectionSuspended = true; + LOGD(TAG, "onConnectionSuspended() was called with cause: " + cause); + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onConnectionSuspended(cause); + } + } + + /** + * Sets the launch options. + * + * @param relaunchIfRunning Launches the app even if the same application is running on the + * receiver + * @param locale The {@link Locale} + */ + public void setLaunchOptions(boolean relaunchIfRunning, Locale locale) { + mLaunchOptions = new LaunchOptions.Builder().setLocale(locale) + .setRelaunchIfRunning(relaunchIfRunning).build(); + } + + /* + * Launches application. For this to succeed, a connection should be already established by the + * CastClient. + */ + private void launchApp() throws TransientNetworkDisconnectionException, NoConnectionException { + LOGD(TAG, "launchApp() is called"); + if (!isConnected()) { + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + setReconnectionStatus(RECONNECTION_STATUS_INACTIVE); + return; + } + checkConnectivity(); + } + + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + LOGD(TAG, "Attempting to join a previously interrupted session..."); + String sessionId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_SESSION_ID); + LOGD(TAG, "joinApplication() -> start"); + Cast.CastApi.joinApplication(mApiClient, mApplicationId, sessionId).setResultCallback( + new ResultCallback() { + + @Override + public void onResult(ApplicationConnectionResult result) { + if (result.getStatus().isSuccess()) { + LOGD(TAG, "joinApplication() -> success"); + onApplicationConnected(result.getApplicationMetadata(), + result.getApplicationStatus(), result.getSessionId(), + result.getWasLaunched()); + } else { + LOGD(TAG, "joinApplication() -> failure"); + clearPersistedConnectionInfo(CLEAR_SESSION | CLEAR_MEDIA_END); + cancelReconnectionTask(); + onApplicationConnectionFailed(result.getStatus().getStatusCode()); + } + } + } + ); + } else { + LOGD(TAG, "Launching app"); + Cast.CastApi.launchApplication(mApiClient, mApplicationId, mLaunchOptions) + .setResultCallback( + new ResultCallback() { + + @Override + public void onResult(ApplicationConnectionResult result) { + if (result.getStatus().isSuccess()) { + LOGD(TAG, "launchApplication() -> success result"); + onApplicationConnected(result.getApplicationMetadata(), + result.getApplicationStatus(), + result.getSessionId(), + result.getWasLaunched()); + } else { + LOGD(TAG, "launchApplication() -> failure result"); + onApplicationConnectionFailed( + result.getStatus().getStatusCode()); + } + } + } + ); + } + } + + /** + * Stops the application on the receiver device. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public final void stopApplication() + throws TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + Cast.CastApi.stopApplication(mApiClient, mSessionId).setResultCallback( + new ResultCallback() { + + @Override + public void onResult(Status result) { + if (!result.isSuccess()) { + LOGD(TAG, "stopApplication -> onResult: stopping " + + "application failed"); + onApplicationStopFailed(result.getStatusCode()); + } else { + LOGD(TAG, "stopApplication -> onResult Stopped application " + + "successfully"); + } + } + }); + } + + /** + * Registers a {@link BaseCastConsumer} interface with this class. Registered listeners will be + * notified of changes to a variety of lifecycle callbacks that the interface provides. + * + * @see {@code BaseCastConsumerImpl} + */ + public final void addBaseCastConsumer(BaseCastConsumer listener) { + if (listener != null) { + if (mBaseCastConsumers.add(listener)) { + LOGD(TAG, "Successfully added the new BaseCastConsumer listener " + listener); + } + } + } + + /** + * Unregisters a {@link BaseCastConsumer}. + */ + public final void removeBaseCastConsumer(BaseCastConsumer listener) { + if (listener != null) { + if (mBaseCastConsumers.remove(listener)) { + LOGD(TAG, "Successfully removed the existing BaseCastConsumer listener " + + listener); + } + } + } + + /** + * A simple method that throws an exception if there is no connectivity to the cast device. + * + * @throws TransientNetworkDisconnectionException If framework is still trying to recover + * @throws NoConnectionException If no connectivity to the device exists + */ + public final void checkConnectivity() throws TransientNetworkDisconnectionException, + NoConnectionException { + if (!isConnected()) { + if (mConnectionSuspended) { + throw new TransientNetworkDisconnectionException(); + } else { + throw new NoConnectionException(); + } + } + } + + @Override + public void onFailed(int resourceId, int statusCode) { + LOGD(TAG, "onFailed() was called with statusCode: " + statusCode); + for (BaseCastConsumer consumer : mBaseCastConsumers) { + consumer.onFailed(resourceId, statusCode); + } + } + + /** + * Returns the version of this library. + */ + public static final String getCclVersion() { + return sCclVersion; + } + + public PreferenceAccessor getPreferenceAccessor() { + return mPreferenceAccessor; + } + + /** + * Clears the persisted connection information. Bitwise OR combination of the following options + * should be passed as the argument: + *
    + *
  • CLEAR_SESSION
  • + *
  • CLEAR_ROUTE
  • + *
  • CLEAR_WIFI
  • + *
  • CLEAR_MEDIA_END
  • + *
  • CLEAR_ALL
  • + *
+ * Clients can form an or + */ + public final void clearPersistedConnectionInfo(int what) { + LOGD(TAG, "clearPersistedConnectionInfo(): Clearing persisted data for " + what); + if (isFlagSet(what, CLEAR_SESSION)) { + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SESSION_ID, null); + } + if (isFlagSet(what, CLEAR_ROUTE)) { + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_ROUTE_ID, null); + } + if (isFlagSet(what, CLEAR_WIFI)) { + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SSID, null); + } + if (isFlagSet(what, CLEAR_MEDIA_END)) { + mPreferenceAccessor.saveLongToPreference(PREFS_KEY_MEDIA_END, null); + } + } + + private static boolean isFlagSet(int mask, int flag) { + return (mask == CLEAR_ALL) || ((mask & flag) == flag); + } + + protected void startReconnectionService(long mediaDurationLeft) { + if (!isFeatureEnabled(FEATURE_WIFI_RECONNECT)) { + return; + } + LOGD(TAG, "startReconnectionService() for media length lef = " + mediaDurationLeft); + long endTime = SystemClock.elapsedRealtime() + mediaDurationLeft; + mPreferenceAccessor.saveLongToPreference(PREFS_KEY_MEDIA_END, endTime); + Context applicationContext = mContext.getApplicationContext(); + Intent service = new Intent(applicationContext, ReconnectionService.class); + service.setPackage(applicationContext.getPackageName()); + applicationContext.startService(service); + } + + protected void stopReconnectionService() { + if (!isFeatureEnabled(FEATURE_WIFI_RECONNECT)) { + return; + } + LOGD(TAG, "stopReconnectionService()"); + Context applicationContext = mContext.getApplicationContext(); + Intent service = new Intent(applicationContext, ReconnectionService.class); + service.setPackage(applicationContext.getPackageName()); + applicationContext.stopService(service); + } + + /** + * A Handler.Callback to receive individual messages when UI goes hidden or becomes visible. + */ + private class UpdateUiVisibilityHandlerCallback implements Handler.Callback { + + @Override + public boolean handleMessage(Message msg) { + onUiVisibilityChanged(msg.what == WHAT_UI_VISIBLE); + return true; + } + } + + /** + * Returns {@code true} if and only if there is at least one route matching the + * {@link #getMediaRouteSelector()}. + */ + public boolean isAnyRouteAvailable() { + return mMediaRouterCallback.isRouteAvailable(); + } +} diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/CastMediaRouterCallback.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/CastMediaRouterCallback.java new file mode 100644 index 000000000..e85270821 --- /dev/null +++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/CastMediaRouterCallback.java @@ -0,0 +1,122 @@ +/* + * 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; + +import static com.google.android.libraries.cast.companionlibrary.utils.LogUtils.LOGD; + +import com.google.android.gms.cast.CastDevice; +import com.google.android.libraries.cast.companionlibrary.utils.LogUtils; + +import android.support.v7.media.MediaRouter; +import android.support.v7.media.MediaRouter.RouteInfo; + +/** + * Provides a handy implementation of {@link MediaRouter.Callback}. When a {@link RouteInfo} is + * selected by user from the list of available routes, this class will call the + * {@link BaseCastManager#setDevice(CastDevice))} of the listener that was passed to it in + * the constructor. In addition, as soon as a non-default route is discovered, the + * {@link BaseCastManager#onCastDeviceDetected(RouteInfo))} is called. + *

+ * There is also some logic in this class to help with the process of previous session recovery. + */ +public class CastMediaRouterCallback extends MediaRouter.Callback { + private static final String TAG = LogUtils.makeLogTag(CastMediaRouterCallback.class); + private final BaseCastManager mCastManager; + private boolean mRouteAvailable = false; + + public CastMediaRouterCallback(BaseCastManager castManager) { + mCastManager = castManager; + } + + @Override + public void onRouteSelected(MediaRouter router, RouteInfo info) { + LOGD(TAG, "onRouteSelected: info=" + info); + if (mCastManager.getReconnectionStatus() + == BaseCastManager.RECONNECTION_STATUS_FINALIZED) { + mCastManager.setReconnectionStatus(BaseCastManager.RECONNECTION_STATUS_INACTIVE); + mCastManager.cancelReconnectionTask(); + return; + } + mCastManager.getPreferenceAccessor().saveStringToPreference( + BaseCastManager.PREFS_KEY_ROUTE_ID, info.getId()); + CastDevice device = CastDevice.getFromBundle(info.getExtras()); + mCastManager.onDeviceSelected(device); + LOGD(TAG, "onRouteSelected: mSelectedDevice=" + device.getFriendlyName()); + } + + @Override + public void onRouteUnselected(MediaRouter router, RouteInfo route) { + LOGD(TAG, "onRouteUnselected: route=" + route); + mCastManager.onDeviceSelected(null); + } + + @Override + public void onRouteAdded(MediaRouter router, RouteInfo route) { + if (!router.getDefaultRoute().equals(route)) { + notifyRouteAvailabilityChangedIfNeeded(router); + mCastManager.onCastDeviceDetected(route); + } + if (mCastManager.getReconnectionStatus() + == BaseCastManager.RECONNECTION_STATUS_STARTED) { + String routeId = mCastManager.getPreferenceAccessor().getStringFromPreference( + BaseCastManager.PREFS_KEY_ROUTE_ID); + if (route.getId().equals(routeId)) { + // we found the route, so lets go with that + LOGD(TAG, "onRouteAdded: Attempting to recover a session with info=" + route); + mCastManager.setReconnectionStatus(BaseCastManager.RECONNECTION_STATUS_IN_PROGRESS); + + CastDevice device = CastDevice.getFromBundle(route.getExtras()); + LOGD(TAG, "onRouteAdded: Attempting to recover a session with device: " + + device.getFriendlyName()); + mCastManager.onDeviceSelected(device); + } + } + } + + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo route) { + notifyRouteAvailabilityChangedIfNeeded(router); + } + + @Override + public void onRouteChanged(MediaRouter router, RouteInfo route) { + notifyRouteAvailabilityChangedIfNeeded(router); + } + + private void notifyRouteAvailabilityChangedIfNeeded(MediaRouter router) { + boolean routeAvailable = isRouteAvailable(router); + if (routeAvailable != mRouteAvailable) { + // availability of routes have changed + mRouteAvailable = routeAvailable; + mCastManager.onCastAvailabilityChanged(mRouteAvailable); + } + } + + private boolean isRouteAvailable(MediaRouter router) { + return router.isRouteAvailable(mCastManager.getMediaRouteSelector(), + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE + | MediaRouter.AVAILABILITY_FLAG_REQUIRE_MATCH); + } + + /** + * Returns {@code true} if and only if there is at least one route matching the + * {@link BaseCastManager#getMediaRouteSelector()}. + */ + public boolean isRouteAvailable() { + return mRouteAvailable; + } +} diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/DataCastManager.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/DataCastManager.java new file mode 100644 index 000000000..d38cac394 --- /dev/null +++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/DataCastManager.java @@ -0,0 +1,462 @@ +/* + * 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; + +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.ApplicationMetadata; +import com.google.android.gms.cast.Cast; +import com.google.android.gms.cast.Cast.CastOptions.Builder; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastStatusCodes; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumer; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumerImpl; +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.LogUtils; + +import android.content.Context; +import android.support.v7.app.MediaRouteDialogFactory; +import android.support.v7.media.MediaRouter.RouteInfo; +import android.text.TextUtils; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * A concrete subclass of {@link BaseCastManager} that is suitable for data-centric applications + * that use multiple namespaces. + *

+ * This is a singleton that needs to be "initialized" (by calling initialize()) prior + * to usage. Subsequent to initialization, an easier way to get access to the singleton class is to + * call a variant of getInstance(). After initialization, callers can enable any + * available feature (all features are off by default). To do so, call enableFeature() + * and pass an OR-ed expression built from one ore more of the following constants: + *

+ *

    + *
  • FEATURE_DEBUGGING: to enable Google Play Services level logging
  • + *
+ * Beyond managing the connectivity to a cast device, this class provides easy-to-use methods to + * send and receive messages using one or more namespaces. These namespaces can be configured during + * the initialization as part of the call to initialize() or can be added later on. + * Clients can subclass this class to extend the features and functionality beyond what this class + * provides. This class manages various states of the remote cast device. Client applications, + * however, can complement the default behavior of this class by hooking into various callbacks that + * it provides (see + * {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumer}). + * Since the number of these callbacks is usually much larger than what a single application might + * be interested in, there is a no-op implementation of this interface (see + * {@link DataCastConsumerImpl}) that applications can subclass to override only those methods that + * they are interested in. Since this library depends on the cast functionalities provided by the + * Google Play services, the library checks to ensure that the right version of that service is + * installed. It also provides a simple static method {@code checkGooglePlayServices()} that clients + * can call at an early stage of their applications to provide a dialog for users if they need to + * update/activate their Google Play Services library. To learn more about this library, please read + * the documentation that is distributed as part of this library. + */ +public class DataCastManager extends BaseCastManager implements Cast.MessageReceivedCallback { + + private static final String TAG = LogUtils.makeLogTag(DataCastManager.class); + private static DataCastManager sInstance; + private final Set mNamespaceList = new HashSet<>(); + private final Set mDataConsumers = new CopyOnWriteArraySet<>(); + + private DataCastManager() { + } + + /** + * Initializes the DataCastManager for clients. Before clients can use DataCastManager, they + * need to initialize it by calling this static method. Then clients can obtain an instance of + * this singleton class by calling {@link DataCastManager#getInstance()}. Failing to initialize + * this class before requesting an instance will result in a {@link CastException} exception. + * + * @param context + * @param applicationId the application ID for your application + * @param namespaces Namespaces to be set up for this class. + */ + public static synchronized DataCastManager 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 DataCastManager(context, applicationId, namespaces); + } + return sInstance; + } + + protected DataCastManager(Context context, String applicationId, String... namespaces) { + super(context, applicationId); + if (namespaces != null) { + for (String namespace : namespaces) { + if (!TextUtils.isEmpty(namespace)) { + mNamespaceList.add(namespace); + } else { + LOGD(TAG, "A null or empty namespace was ignored."); + } + } + } + } + + /** + * Returns a (singleton) instance of this class. Clients should call this method in order to + * get a hold of this singleton instance. If it is not initialized yet, a + * {@link CastException} will be thrown. + */ + public static DataCastManager 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; + } + + /** + * Adds a channel with the given {@code namespace} and registers {@link DataCastManager} as + * the callback receiver. If the namespace is already registered, this returns + * false, otherwise returns true. + * + * @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 boolean addNamespace(String namespace) throws + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (TextUtils.isEmpty(namespace)) { + throw new IllegalArgumentException("namespace cannot be empty"); + } + if (mNamespaceList.contains(namespace)) { + LOGD(TAG, "Ignoring to add a namespace that is already added."); + return false; + } + try { + Cast.CastApi.setMessageReceivedCallbacks(mApiClient, namespace, this); + mNamespaceList.add(namespace); + return true; + } catch (IOException | IllegalStateException e) { + LOGE(TAG, String.format("addNamespace(%s)", namespace), e); + } + return false; + } + + /** + * Unregisters a namespace. If namespace is not already registered, it returns + * false, otherwise a successful removal returns true. + * + * @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 boolean removeNamespace(String namespace) throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + if (TextUtils.isEmpty(namespace)) { + throw new IllegalArgumentException("namespace cannot be empty"); + } + if (!mNamespaceList.contains(namespace)) { + LOGD(TAG, "Ignoring to remove a namespace that is not registered."); + return false; + } + try { + Cast.CastApi.removeMessageReceivedCallbacks(mApiClient, namespace); + mNamespaceList.remove(namespace); + return true; + } catch (IOException | IllegalStateException e) { + LOGE(TAG, String.format("removeNamespace(%s)", namespace), e); + } + return false; + + } + + /** + * Sends the message on the data channel for the namespace. If fails, + * it will call onMessageSendFailed + * + * @throws IllegalArgumentException If the the message is null, empty, or too long; or if the + * namespace is null or too long. + * @throws IllegalStateException If there is no active service connection. + * @throws IOException + */ + public void sendDataMessage(String message, String namespace) + throws IllegalArgumentException, IllegalStateException, IOException { + checkConnectivity(); + if (TextUtils.isEmpty(namespace)) { + throw new IllegalArgumentException("namespace cannot be empty"); + } + Cast.CastApi.sendMessage(mApiClient, namespace, message). + setResultCallback(new ResultCallback() { + + @Override + public void onResult(Status result) { + if (!result.isSuccess()) { + DataCastManager.this.onMessageSendFailed(result); + } + } + }); + } + + @Override + protected void onDeviceUnselected() { + detachDataChannels(); + } + + @Override + protected Builder getCastOptionBuilder(CastDevice device) { + + Builder builder = Cast.CastOptions.builder( + mSelectedCastDevice, new CastListener()); + if (isFeatureEnabled(FEATURE_DEBUGGING)) { + builder.setVerboseLoggingEnabled(true); + } + return builder; + } + + class CastListener extends Cast.Listener { + + /* + * (non-Javadoc) + * @see com.google.android.gms.cast.Cast.Listener#onApplicationDisconnected (int) + */ + @Override + public void onApplicationDisconnected(int statusCode) { + DataCastManager.this.onApplicationDisconnected(statusCode); + } + + /* + * (non-Javadoc) + * @see com.google.android.gms.cast.Cast.Listener#onApplicationStatusChanged () + */ + @Override + public void onApplicationStatusChanged() { + DataCastManager.this.onApplicationStatusChanged(); + } + } + + @Override + protected MediaRouteDialogFactory getMediaRouteDialogFactory() { + return null; + } + + @Override + public void onApplicationConnected(ApplicationMetadata appMetadata, String applicationStatus, + String sessionId, boolean wasLaunched) { + LOGD(TAG, "onApplicationConnected() reached with sessionId: " + sessionId); + + // saving session for future retrieval; we only save the last session info + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SESSION_ID, sessionId); + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + // we have tried to reconnect and successfully launched the app, so + // it is time to select the route and make the cast icon happy :-) + List routes = mMediaRouter.getRoutes(); + if (routes != null) { + String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID); + boolean found = false; + for (RouteInfo routeInfo : routes) { + if (routeId.equals(routeInfo.getId())) { + // found the right route + LOGD(TAG, "Found the correct route during reconnection attempt"); + found = true; + mReconnectionStatus = RECONNECTION_STATUS_FINALIZED; + mMediaRouter.selectRoute(routeInfo); + break; + } + } + if (!found) { + // we were hoping to have the route that we wanted, but we + // didn't so we deselect the device + onDeviceSelected(null); + mReconnectionStatus = RECONNECTION_STATUS_INACTIVE; + return; + } + } + } + // registering namespaces, if any + try { + attachDataChannels(); + mSessionId = sessionId; + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onApplicationConnected(appMetadata, applicationStatus, sessionId, + wasLaunched); + } + } catch (IllegalStateException | IOException e) { + LOGE(TAG, "Failed to attach namespaces", e); + } + + } + + /* + * Adds namespaces for data channel(s) + * + * @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 + * @throws IOException If an I/O error occurs while performing the request. + * @throws IllegalStateException Thrown when the controller is not connected to a CastDevice. + * @throws IllegalArgumentException If namespace is null. + */ + private void attachDataChannels() throws IllegalStateException, IOException { + checkConnectivity(); + for (String namespace : mNamespaceList) { + Cast.CastApi.setMessageReceivedCallbacks(mApiClient, namespace, this); + } + } + + /* + * Remove namespaces + * + * @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 + */ + private void detachDataChannels() { + if (mApiClient == null) { + return; + } + for (String namespace : mNamespaceList) { + try { + Cast.CastApi.removeMessageReceivedCallbacks(mApiClient, namespace); + } catch (IOException | IllegalArgumentException e) { + LOGE(TAG, "detachDataChannels() Failed to remove namespace: " + namespace, e); + } + } + } + + @Override + public void onApplicationConnectionFailed(int errorCode) { + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + if (errorCode == CastStatusCodes.APPLICATION_NOT_RUNNING) { + // while trying to re-establish session, we found out that the app is not running + // so we need to disconnect + mReconnectionStatus = RECONNECTION_STATUS_INACTIVE; + onDeviceSelected(null); + } + } else { + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onApplicationConnectionFailed(errorCode); + } + onDeviceSelected(null); + if (mMediaRouter != null) { + LOGD(TAG, "onApplicationConnectionFailed(): Setting route to default"); + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + } + } + + public void onApplicationDisconnected(int errorCode) { + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onApplicationDisconnected(errorCode); + } + if (mMediaRouter != null) { + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + onDeviceSelected(null); + + } + + public void onApplicationStatusChanged() { + String appStatus; + if (!isConnected()) { + return; + } + try { + appStatus = Cast.CastApi.getApplicationStatus(mApiClient); + LOGD(TAG, "onApplicationStatusChanged() reached: " + appStatus); + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onApplicationStatusChanged(appStatus); + } + } catch (IllegalStateException e) { + LOGE(TAG, "onApplicationStatusChanged(): Failed", e); + } + + } + + @Override + public void onApplicationStopFailed(int errorCode) { + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onApplicationStopFailed(errorCode); + } + } + + @Override + public void onConnectivityRecovered() { + try { + attachDataChannels(); + } catch (IOException | IllegalStateException e) { + LOGE(TAG, "onConnectivityRecovered(): Failed to reattach data channels", e); + } + super.onConnectivityRecovered(); + } + + @Override + public void onMessageReceived(CastDevice castDevice, String namespace, String message) { + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onMessageReceived(castDevice, namespace, message); + } + } + + public void onMessageSendFailed(Status result) { + for (DataCastConsumer consumer : mDataConsumers) { + consumer.onMessageSendFailed(result); + } + } + + /** + * Registers an + * {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumer} + * interface with this class. Registered listeners will be notified of changes to a variety of + * lifecycle and status changes through the callbacks that the interface provides. + */ + public void addDataCastConsumer(DataCastConsumer listener) { + if (listener != null) { + addBaseCastConsumer(listener); + boolean result; + result = mDataConsumers.add(listener); + if (result) { + LOGD(TAG, "Successfully added the new DataCastConsumer listener " + listener); + } else { + LOGD(TAG, "Adding Listener " + listener + " was already registered, " + + "skipping this step"); + } + } + } + + /** + * Unregisters an {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.DataCastConsumer}. + */ + public void removeDataCastConsumer(DataCastConsumer listener) { + if (listener != null) { + removeBaseCastConsumer(listener); + mDataConsumers.remove(listener); + } + } + +} diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/MediaQueue.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/MediaQueue.java new file mode 100644 index 000000000..6b376f97b --- /dev/null +++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/MediaQueue.java @@ -0,0 +1,110 @@ +/* + * 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; + +import com.google.android.gms.cast.MediaQueueItem; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A simple class to model a queue for bookkeeping purposes. + */ +public class MediaQueue { + + private List mQueueItems = new CopyOnWriteArrayList<>(); + public static final int INVALID_POSITION = -1; + private MediaQueueItem mCurrentItem; + private boolean mShuffle; + private int mRepeatMode; + + public MediaQueue() {} + + public MediaQueue(List queueItems, + MediaQueueItem currentItem, boolean shuffle, int repeatMode) { + mQueueItems = queueItems; + mCurrentItem = currentItem; + mShuffle = shuffle; + mRepeatMode = repeatMode; + } + + public final List getQueueItems() { + return mQueueItems; + } + + public final void setQueueItems(List queue) { + if (queue == null) { + mQueueItems = null; + } else { + mQueueItems = new CopyOnWriteArrayList<>(queue); + } + } + + public final MediaQueueItem getCurrentItem() { + return mCurrentItem; + } + + public final void setCurrentItem(MediaQueueItem currentItem) { + mCurrentItem = currentItem; + } + + public final boolean isShuffle() { + return mShuffle; + } + + public final void setShuffle(boolean shuffle) { + mShuffle = shuffle; + } + + public final int getRepeatMode() { + return mRepeatMode; + } + + public final void setRepeatMode(int repeatMode) { + mRepeatMode = repeatMode; + } + + /** + * Returns the size of queue, or 0 if it is {@code null} + */ + public final int getCount() { + return mQueueItems == null || mQueueItems.isEmpty() ? 0 : mQueueItems.size(); + } + + /** + * Returns {@code true} if and only if the queue is empty or {@code null} + */ + public final boolean isEmpty() { + return mQueueItems == null || mQueueItems.isEmpty(); + } + + /** + * Returns the position of the current item in the queue. If the queue is {@code null}, it + * will return {@link #INVALID_POSITION}. If the queue is empty, it returns 0. + */ + public final int getCurrentItemPosition() { + if (mQueueItems == null) { + return INVALID_POSITION; + } + + if (mQueueItems.isEmpty()) { + return 0; + } + + return mQueueItems.indexOf(mCurrentItem); + } +} diff --git a/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/VideoCastManager.java b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/VideoCastManager.java new file mode 100644 index 000000000..50ec932f7 --- /dev/null +++ b/CCL/src/main/java/com/google/android/libraries/cast/companionlibrary/cast/VideoCastManager.java @@ -0,0 +1,2977 @@ +/* + * 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; + +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.ApplicationMetadata; +import com.google.android.gms.cast.Cast; +import com.google.android.gms.cast.Cast.CastOptions.Builder; +import com.google.android.gms.cast.Cast.MessageReceivedCallback; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastStatusCodes; +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.gms.cast.RemoteMediaPlayer.MediaChannelResult; +import com.google.android.gms.cast.TextTrackStyle; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.images.WebImage; +import com.google.android.libraries.cast.companionlibrary.R; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer; +import com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumerImpl; +import com.google.android.libraries.cast.companionlibrary.cast.dialog.video.VideoMediaRouteDialogFactory; +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.cast.player.MediaAuthService; +import com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastController; +import com.google.android.libraries.cast.companionlibrary.cast.player.VideoCastControllerActivity; +import com.google.android.libraries.cast.companionlibrary.cast.tracks.OnTracksSelectedListener; +import com.google.android.libraries.cast.companionlibrary.cast.tracks.TracksPreferenceManager; +import com.google.android.libraries.cast.companionlibrary.notification.VideoCastNotificationService; +import com.google.android.libraries.cast.companionlibrary.remotecontrol.VideoIntentReceiver; +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 com.google.android.libraries.cast.companionlibrary.widgets.IMiniController; +import com.google.android.libraries.cast.companionlibrary.widgets.MiniController; +import com.google.android.libraries.cast.companionlibrary.widgets.MiniController.OnMiniControllerChangedListener; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources.NotFoundException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceScreen; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.support.v7.app.MediaRouteDialogFactory; +import android.support.v7.media.MediaRouter.RouteInfo; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.view.accessibility.CaptioningManager; + +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; + +/** + * An abstract subclass of {@link BaseCastManager} that is suitable for casting video contents (it + * also provides a single custom data channel/namespace if an out-of-band communication is + * needed). + *

+ * Clients need to initialize this class by calling + * {@link #initialize(android.content.Context, String, Class, String)} and implement + * the abstracts methods defined here. To access the (singleton) instance of this class, clients + * need to call {@link #getInstance()}. After acquiring an instance, callers can enable a + * number of features (all features are turned off by default). To do so, call + * {@link #enableFeatures(int)} and pass an OR-ed expression built from one or more of the + * following constants: + *

+ *

    + *
  • FEATURE_DEBUGGING: to enable Google Play Services level logging
  • + *
  • FEATURE_NOTIFICATION: to enable system notifications
  • + *
  • FEATURE_LOCKSCREEN: to enable lock-screen controls on supported versions
  • + *
  • FEATURE_WIFI_RECONNECT: to enable reconnection logic
  • + *
  • FEATURE_CAPTIONS_PREFERENCE: to enable Closed Caption Preference handling logic
  • + *
+ * Callers can add {@link MiniController} components to their application pages by adding the + * corresponding widget to their layout xml and then calling addMiniController(). This + * class manages various states of the remote cast device. Client applications, however, can + * complement the default behavior of this class by hooking into various callbacks that it provides + * (see + * {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer}). + * Since the number of these callbacks is usually much larger than what a single application might + * be interested in, there is a no-op implementation of this interface (see + * {@link VideoCastConsumerImpl}) that applications can subclass to override only those methods that + * they are interested in. Since this library depends on the cast functionalities provided by the + * Google Play services, the library checks to ensure that the right version of that service is + * installed. It also provides a simple static method {@code checkGooglePlayServices()} that clients + * can call at an early stage of their applications to provide a dialog for users if they need to + * update/activate their Google Play Services library. To learn more about this library, please read + * the documentation that is distributed as part of this library. + */ +public class VideoCastManager extends BaseCastManager + implements OnMiniControllerChangedListener, OnFailedListener { + + private static final String TAG = LogUtils.makeLogTag(VideoCastManager.class); + + public static final String EXTRA_HAS_AUTH = "hasAuth"; + public static final String EXTRA_MEDIA = "media"; + public static final String EXTRA_START_POINT = "startPoint"; + public static final String EXTRA_SHOULD_START = "shouldStart"; + public static final String EXTRA_CUSTOM_DATA = "customData"; + public static final double DEFAULT_VOLUME_STEP = 0.05; + private static final long PROGRESS_UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1); + private double mVolumeStep = DEFAULT_VOLUME_STEP; + public static final long DEFAULT_LIVE_STREAM_DURATION_MS = TimeUnit.HOURS.toMillis(2); // 2hrs + public static final String PREFS_KEY_START_ACTIVITY = "ccl-start-cast-activity"; + public static final String PREFS_KEY_NEXT_PREV_POLICY = "ccl-next-prev-policy"; + public static final String PREFS_KEY_IMMERSIVE_MODE = "ccl-cast-contoller-immersive"; + private TracksPreferenceManager mTrackManager; + private ComponentName mMediaEventReceiver; + private MediaQueue mMediaQueue; + private MediaStatus mMediaStatus; + private Timer mProgressTimer; + private UpdateProgressTask mProgressTask; + private FetchBitmapTask mLockScreenFetchTask; + private FetchBitmapTask mMediaSessionIconFetchTask; + + /** + * Volume can be controlled at two different layers, one is at the "stream" level and one at + * the "device" level. VolumeType encapsulates these two types. + * + * @see {@link #setVolumeType} + */ + public static enum VolumeType { + STREAM, + DEVICE + } + + private static VideoCastManager sInstance; + private Class mTargetActivity; + private final Set mMiniControllers = Collections + .synchronizedSet(new HashSet()); + private AudioManager mAudioManager; + private RemoteMediaPlayer mRemoteMediaPlayer; + private MediaSessionCompat mMediaSessionCompat; + private VolumeType mVolumeType = VolumeType.DEVICE; + private int mState = MediaStatus.PLAYER_STATE_IDLE; + private int mIdleReason; + private String mDataNamespace; + private Cast.MessageReceivedCallback mDataChannel; + private final Set mVideoConsumers = new CopyOnWriteArraySet<>(); + private final Set mTracksSelectedListeners = + new CopyOnWriteArraySet<>(); + private MediaAuthService mAuthService; + private long mLiveStreamDuration = DEFAULT_LIVE_STREAM_DURATION_MS; + private MediaQueueItem mPreLoadingItem; + + public static final int QUEUE_OPERATION_LOAD = 1; + public static final int QUEUE_OPERATION_INSERT_ITEMS = 2; + public static final int QUEUE_OPERATION_UPDATE_ITEMS = 3; + public static final int QUEUE_OPERATION_JUMP = 4; + public static final int QUEUE_OPERATION_REMOVE_ITEM = 5; + public static final int QUEUE_OPERATION_REMOVE_ITEMS = 6; + public static final int QUEUE_OPERATION_REORDER = 7; + public static final int QUEUE_OPERATION_MOVE = 8; + public static final int QUEUE_OPERATION_APPEND = 9; + public static final int QUEUE_OPERATION_NEXT = 10; + public static final int QUEUE_OPERATION_PREV = 11; + public static final int QUEUE_OPERATION_SET_REPEAT = 12; + + /** + * Returns the namespace for an additional data namespace that this library can manage for an + * application to have an out-of-band communication channel with the receiver. Note that this + * only prepares the sender and your own receiver needs to be able to receive and manage the + * channel as well. The default implementation is not to set up any additional channel. + * + * @return The namespace that the library can manage for the application. If {@code null}, no + * namespace will be set up. + */ + protected String getDataNamespace() { + return null; + } + + private VideoCastManager() { + } + + protected VideoCastManager(Context context, String applicationId, Class targetActivity, + String dataNamespace) { + super(context, applicationId); + LOGD(TAG, "VideoCastManager is instantiated"); + mDataNamespace = dataNamespace; + if (targetActivity == null) { + targetActivity = VideoCastControllerActivity.class; + } + mTargetActivity = targetActivity; + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_CAST_ACTIVITY_NAME, + mTargetActivity.getName()); + if (!TextUtils.isEmpty(mDataNamespace)) { + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_CAST_CUSTOM_DATA_NAMESPACE, + dataNamespace); + } + + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + + public static synchronized VideoCastManager initialize(Context context, + String applicationId, Class targetActivity, String dataNamespace) { + if (sInstance == null) { + LOGD(TAG, "New instance of VideoCastManager is created"); + if (ConnectionResult.SUCCESS != GooglePlayServicesUtil + .isGooglePlayServicesAvailable(context)) { + String msg = "Couldn't find the appropriate version of Google Play Services"; + LOGE(TAG, msg); + } + sInstance = new VideoCastManager(context, applicationId, targetActivity, dataNamespace); + sInstance.restartProgressTimer(); + } + return sInstance; + } + + + /** + * Returns a (singleton) instance of this class. Clients should call this method in order to + * get a hold of this singleton instance, only after it is initialized. If it is not initialized + * yet, an {@link IllegalStateException} will be thrown. + * + */ + public static VideoCastManager getInstance() { + if (sInstance == null) { + String msg = "No VideoCastManager instance was found, did you forget to initialize it?"; + LOGE(TAG, msg); + throw new IllegalStateException(msg); + } + return sInstance; + } + + @Override + protected void onFeaturesUpdated(int capabilities) { + if (isFeatureEnabled(FEATURE_CAPTIONS_PREFERENCE)) { + mTrackManager = new TracksPreferenceManager(mContext.getApplicationContext()); + registerCaptionListener(mContext.getApplicationContext()); + } + } + + /** + * Updates the information and state of a MiniController. + * + * @throws TransientNetworkDisconnectionException + * @throws NoConnectionException + */ + private void updateMiniController(IMiniController controller) + throws TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + checkRemoteMediaPlayerAvailable(); + if (mRemoteMediaPlayer.getStreamDuration() > 0 || isRemoteStreamLive()) { + MediaInfo mediaInfo = getRemoteMediaInformation(); + MediaMetadata mm = mediaInfo.getMetadata(); + controller.setStreamType(mediaInfo.getStreamType()); + controller.setPlaybackStatus(mState, mIdleReason); + controller.setSubtitle(mContext.getResources().getString(R.string.ccl_casting_to_device, + mDeviceName)); + controller.setTitle(mm.getString(MediaMetadata.KEY_TITLE)); + controller.setIcon(Utils.getImageUri(mediaInfo, 0)); + } + } + + /* + * Updates the information and state of all MiniControllers + */ + private void updateMiniControllers() { + synchronized (mMiniControllers) { + for (final IMiniController controller : mMiniControllers) { + try { + updateMiniController(controller); + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + LOGE(TAG, "updateMiniControllers() Failed to update mini controller", e); + } + } + } + } + + /* + * (non-Javadoc) + * @see com.google.android.libraries.cast.companionlibrary.widgets.MiniController. + * OnMiniControllerChangedListener#onPlayPauseClicked(android.view.View) + * @throws TransientNetworkDisconnectionException + * @throws NoConnectionException + * @throws CastException + */ + @Override + public void onPlayPauseClicked(View v) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (mState == MediaStatus.PLAYER_STATE_PLAYING) { + pause(); + } else { + boolean isLive = isRemoteStreamLive(); + if ((mState == MediaStatus.PLAYER_STATE_PAUSED && !isLive) + || (mState == MediaStatus.PLAYER_STATE_IDLE && isLive)) { + play(); + } + } + } + + /* + * (non-Javadoc) + * @see com.google.android.libraries.cast.companionlibrary.widgets.MiniController. + * OnMiniControllerChangedListener #onTargetActivityInvoked(android.content.Context) + */ + @Override + public void onTargetActivityInvoked(Context context) throws + TransientNetworkDisconnectionException, NoConnectionException { + Intent intent = new Intent(context, mTargetActivity); + intent.putExtra(EXTRA_MEDIA, Utils.mediaInfoToBundle(getRemoteMediaInformation())); + context.startActivity(intent); + } + + @Override + public void onUpcomingPlayClicked(View view, MediaQueueItem upcomingItem) { + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onUpcomingPlayClicked(view, upcomingItem); + } + } + + @Override + public void onUpcomingStopClicked(View view, MediaQueueItem upcomingItem) { + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onUpcomingStopClicked(view, upcomingItem); + } + } + + /** + * Updates the visibility of the mini controllers. In most cases, clients do not need to use + * this as the {@link VideoCastManager} handles the visibility. + * + * @param visible If {@link true}, mini controllers wil be visible; hidden otherwise. + */ + public void updateMiniControllersVisibility(boolean visible) { + LOGD(TAG, "updateMiniControllersVisibility() reached with visibility: " + visible); + synchronized (mMiniControllers) { + for (IMiniController controller : mMiniControllers) { + controller.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + } + + public void updateMiniControllersVisibilityForUpcoming(MediaQueueItem item) { + synchronized (mMiniControllers) { + for (IMiniController controller : mMiniControllers) { + controller.setUpcomingItem(item); + controller.setUpcomingVisibility(item != null); + } + } + } + + /** + * Sets an internal flag that is used to disambiguate the two cases that the + * {@code VideoCastControllerActivity} is started programmatically or through the system (say, + * after configuration change or from recent history). + */ + private void setFlagForStartCastControllerActivity() { + mPreferenceAccessor.saveBooleanToPreference(PREFS_KEY_START_ACTIVITY, true); + } + + /** + * Launches the VideoCastControllerActivity that provides a default Cast Player page. + * + * @param context The context to use for starting the activity + * @param mediaWrapper a bundle wrapper for the media that is or will be casted + * @param position Starting point, in milliseconds, of the media playback + * @param shouldStart indicates if the remote playback should start after launching the new + * page + * @param customData Optional {@link JSONObject} + */ + public void startVideoCastControllerActivity(Context context, Bundle mediaWrapper, int position, + boolean shouldStart, JSONObject customData) { + Intent intent = new Intent(context, VideoCastControllerActivity.class); + intent.putExtra(EXTRA_MEDIA, mediaWrapper); + intent.putExtra(EXTRA_START_POINT, position); + intent.putExtra(EXTRA_SHOULD_START, shouldStart); + if (customData != null) { + intent.putExtra(EXTRA_CUSTOM_DATA, customData.toString()); + } + setFlagForStartCastControllerActivity(); + context.startActivity(intent); + } + + /** + * Launches the {@link VideoCastControllerActivity} that provides a default Cast Player page. + * + * @param context The context to use for starting the activity + * @param mediaWrapper A bundle wrapper for the media that is or will be casted + * @param position Starting point, in milliseconds, of the media playback + * @param shouldStart Indicates if the remote playback should start after launching the new + * page + */ + public void startVideoCastControllerActivity(Context context, Bundle mediaWrapper, int position, + boolean shouldStart) { + startVideoCastControllerActivity(context, mediaWrapper, position, shouldStart, null); + } + + /** + * Launches the {@link VideoCastControllerActivity} that provides a default Cast Player page. + * This variation should be used when an + * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthService} + * needs to be used. + */ + public void startVideoCastControllerActivity(Context context, MediaAuthService authService) { + if (authService != null) { + mAuthService = authService; + Intent intent = new Intent(context, VideoCastControllerActivity.class); + intent.putExtra(EXTRA_HAS_AUTH, true); + setFlagForStartCastControllerActivity(); + context.startActivity(intent); + } + } + + /** + * Launches the {@link VideoCastControllerActivity} that provides a default Cast Player page. + * + * @param context The context to use for starting the activity + * @param mediaInfo The media that is or will be casted + * @param position Starting point, in milliseconds, of the media playback + * @param shouldStart Indicates if the remote playback should start after launching the new page + */ + public void startVideoCastControllerActivity(Context context, + MediaInfo mediaInfo, int position, boolean shouldStart) { + startVideoCastControllerActivity(context, Utils.mediaInfoToBundle(mediaInfo), position, + shouldStart); + } + + /** + * Returns the instance of + * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthService}, + * or null if there is no such instance. + */ + public MediaAuthService getMediaAuthService() { + return mAuthService; + } + + /** + * Sets the + * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthService}. + */ + public void setMediaAuthService(MediaAuthService authService) { + mAuthService = authService; + } + + /** + * Removes the pointer to the + * {@link com.google.android.libraries.cast.companionlibrary.cast.player.MediaAuthService} to + * avoid any leak. + */ + public void removeMediaAuthService() { + mAuthService = null; + } + + /** + * Returns the active {@link RemoteMediaPlayer} instance. Since there are a number of media + * control APIs that this library do not provide a wrapper for, client applications can call + * those methods directly after obtaining an instance of the active {@link RemoteMediaPlayer}. + */ + public final RemoteMediaPlayer getRemoteMediaPlayer() { + return mRemoteMediaPlayer; + } + + /** + * Determines if the media that is loaded remotely is a live stream or not. + * + * @throws TransientNetworkDisconnectionException + * @throws NoConnectionException + */ + public final boolean isRemoteStreamLive() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + MediaInfo info = getRemoteMediaInformation(); + return (info != null) && (info.getStreamType() == MediaInfo.STREAM_TYPE_LIVE); + } + + /** + * A helper method to determine if, given a player state and an idle reason (if the state is + * idle) will warrant having a UI for remote presentation of the remote content. + * + * @throws TransientNetworkDisconnectionException + * @throws NoConnectionException + */ + public boolean shouldRemoteUiBeVisible(int state, int idleReason) + throws TransientNetworkDisconnectionException, NoConnectionException { + switch (state) { + case MediaStatus.PLAYER_STATE_PLAYING: + case MediaStatus.PLAYER_STATE_PAUSED: + case MediaStatus.PLAYER_STATE_BUFFERING: + return true; + case MediaStatus.PLAYER_STATE_IDLE: + return isRemoteStreamLive() && (idleReason == MediaStatus.IDLE_REASON_CANCELED); + default: + } + return false; + } + + /* + * A simple check to make sure mRemoteMediaPlayer is not null + */ + private void checkRemoteMediaPlayerAvailable() throws NoConnectionException { + if (mRemoteMediaPlayer == null) { + throw new NoConnectionException(); + } + } + + /** + * Sets the type of volume. Most applications should use {@code VolumeType.DEVICE} (which is + * the default value) but in rare cases, an application can set the type to {@code + * VolumeType.STREAM} + */ + public final void setVolumeType(VolumeType volumeType) { + mVolumeType = volumeType; + } + + /** + * Returns the url for the movie that is currently playing on the remote device. If there is no + * connection, this will return 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 String getRemoteMediaUrl() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + if (mRemoteMediaPlayer != null && mRemoteMediaPlayer.getMediaInfo() != null) { + MediaInfo info = mRemoteMediaPlayer.getMediaInfo(); + mRemoteMediaPlayer.getMediaStatus().getPlayerState(); + return info.getContentId(); + } + throw new NoConnectionException(); + } + + /** + * Indicates if the remote movie is currently playing (or buffering). + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public boolean isRemoteMediaPlaying() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + return mState == MediaStatus.PLAYER_STATE_BUFFERING + || mState == MediaStatus.PLAYER_STATE_PLAYING; + } + + /** + * Returns true if the remote connected device is playing a movie. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public boolean isRemoteMediaPaused() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + return mState == MediaStatus.PLAYER_STATE_PAUSED; + } + + /** + * Returns true only if there is a media on the remote being played, paused or + * buffered. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public boolean isRemoteMediaLoaded() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + return isRemoteMediaPaused() || isRemoteMediaPlaying(); + } + + /** + * Returns the {@link MediaInfo} for the current media + * + * @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 MediaInfo getRemoteMediaInformation() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + checkRemoteMediaPlayerAvailable(); + return mRemoteMediaPlayer.getMediaInfo(); + } + + /** + * Gets the remote's system volume. It internally detects what type of volume is used. + * + * @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 double getVolume() throws TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (mVolumeType == VolumeType.STREAM) { + checkRemoteMediaPlayerAvailable(); + return mRemoteMediaPlayer.getMediaStatus().getStreamVolume(); + } + return getDeviceVolume(); + } + + /** + * Sets the volume. It internally determines if this should be done for stream or + * device volume. + * + * @param volume Should be a value between 0 and 1, inclusive. + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + * @throws CastException If setting system volume fails + * + * @see {link #setVolumeType()} + */ + public void setVolume(double volume) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (volume > 1.0) { + volume = 1.0; + } else if (volume < 0) { + volume = 0.0; + } + if (mVolumeType == VolumeType.STREAM) { + checkRemoteMediaPlayerAvailable(); + mRemoteMediaPlayer.setStreamVolume(mApiClient, volume).setResultCallback( + new ResultCallback() { + + @Override + public void onResult(MediaChannelResult result) { + if (!result.getStatus().isSuccess()) { + onFailed(R.string.ccl_failed_setting_volume, + result.getStatus().getStatusCode()); + } + } + } + ); + } else { + setDeviceVolume(volume); + } + } + + /** + * Increments (or decrements) the volume by the given amount. It internally determines if this + * should be done for stream or device volume. + * + * @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 + * @throws CastException + */ + public void adjustVolume(double delta) throws CastException, + TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + double vol = getVolume() + delta; + if (vol > 1) { + vol = 1; + } else if (vol < 0) { + vol = 0; + } + setVolume(vol); + } + + /** + * Increments or decrements volume by delta if {@code delta < 0} or + * {@code delta > 0}, respectively. Note that the volume range is between 0 and {@code + * RouteInfo.getVolumeMax()}. + */ + public void updateVolume(int delta) { + RouteInfo info = mMediaRouter.getSelectedRoute(); + info.requestUpdateVolume(delta); + } + + /** + * Returns true if remote device is muted. It internally determines if this should + * be done for stream or device volume. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public boolean isMute() throws TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (mVolumeType == VolumeType.STREAM) { + checkRemoteMediaPlayerAvailable(); + return mRemoteMediaPlayer.getMediaStatus().isMute(); + } else { + return isDeviceMute(); + } + } + + /** + * Mutes or un-mutes the volume. It internally determines if this should be done for + * stream or device volume. + * + * @throws CastException + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public void setMute(boolean mute) throws CastException, TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + if (mVolumeType == VolumeType.STREAM) { + checkRemoteMediaPlayerAvailable(); + mRemoteMediaPlayer.setStreamMute(mApiClient, mute); + } else { + setDeviceMute(mute); + } + } + + /** + * Returns the duration of the media that is loaded, in milliseconds. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public long getMediaDuration() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + checkRemoteMediaPlayerAvailable(); + return mRemoteMediaPlayer.getStreamDuration(); + } + + /** + * Returns the time left (in milliseconds) of the current media. If there is no + * {@code RemoteMediaPlayer}, it returns -1. + * + * @throws TransientNetworkDisconnectionException + * @throws NoConnectionException + */ + public long getMediaTimeRemaining() + throws TransientNetworkDisconnectionException, NoConnectionException { + checkConnectivity(); + if (mRemoteMediaPlayer == null) { + return -1; + } + return isRemoteStreamLive() ? mLiveStreamDuration : mRemoteMediaPlayer.getStreamDuration() + - mRemoteMediaPlayer.getApproximateStreamPosition(); + } + + /** + * Returns the current (approximate) position of the current media, in milliseconds. + * + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public long getCurrentMediaPosition() throws TransientNetworkDisconnectionException, + NoConnectionException { + checkConnectivity(); + checkRemoteMediaPlayerAvailable(); + return mRemoteMediaPlayer.getApproximateStreamPosition(); + } + + /* + * 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() { + if (!isFeatureEnabled(FEATURE_NOTIFICATION)) { + return true; + } + LOGD(TAG, "startNotificationService()"); + Intent service = new Intent(mContext, VideoCastNotificationService.class); + service.setPackage(mContext.getPackageName()); + service.setAction(VideoCastNotificationService.ACTION_VISIBILITY); + service.putExtra(VideoCastNotificationService.NOTIFICATION_VISIBILITY, !mUiVisible); + return mContext.startService(service) != null; + } + + private void stopNotificationService() { + if (!isFeatureEnabled(FEATURE_NOTIFICATION)) { + return; + } + if (mContext != null) { + mContext.stopService(new Intent(mContext, VideoCastNotificationService.class)); + } + } + + private void onApplicationDisconnected(int errorCode) { + LOGD(TAG, "onApplicationDisconnected() reached with error code: " + errorCode); + mApplicationErrorCode = errorCode; + updateMediaSession(false); + if (mMediaSessionCompat != null && isFeatureEnabled(FEATURE_LOCKSCREEN)) { + mMediaRouter.setMediaSessionCompat(null); + } + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onApplicationDisconnected(errorCode); + } + if (mMediaRouter != null) { + LOGD(TAG, "onApplicationDisconnected(): Cached RouteInfo: " + getRouteInfo()); + LOGD(TAG, "onApplicationDisconnected(): Selected RouteInfo: " + + mMediaRouter.getSelectedRoute()); + if (getRouteInfo() == null || mMediaRouter.getSelectedRoute().equals(getRouteInfo())) { + LOGD(TAG, "onApplicationDisconnected(): Setting route to default"); + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + } + onDeviceSelected(null); + updateMiniControllersVisibility(false); + stopNotificationService(); + } + + private void onApplicationStatusChanged() { + if (!isConnected()) { + return; + } + try { + String appStatus = Cast.CastApi.getApplicationStatus(mApiClient); + LOGD(TAG, "onApplicationStatusChanged() reached: " + appStatus); + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onApplicationStatusChanged(appStatus); + } + } catch (IllegalStateException e) { + LOGE(TAG, "onApplicationStatusChanged()", e); + } + } + + private void onVolumeChanged() { + LOGD(TAG, "onVolumeChanged() reached"); + double volume; + try { + volume = getVolume(); + boolean isMute = isMute(); + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onVolumeChanged(volume, isMute); + } + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + LOGE(TAG, "Failed to get volume", e); + } + + } + + @Override + protected void onApplicationConnected(ApplicationMetadata appMetadata, + String applicationStatus, String sessionId, boolean wasLaunched) { + LOGD(TAG, "onApplicationConnected() reached with sessionId: " + sessionId + + ", and mReconnectionStatus=" + mReconnectionStatus); + mApplicationErrorCode = NO_APPLICATION_ERROR; + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + // we have tried to reconnect and successfully launched the app, so + // it is time to select the route and make the cast icon happy :-) + List routes = mMediaRouter.getRoutes(); + if (routes != null) { + String routeId = mPreferenceAccessor.getStringFromPreference(PREFS_KEY_ROUTE_ID); + for (RouteInfo routeInfo : routes) { + if (routeId.equals(routeInfo.getId())) { + // found the right route + LOGD(TAG, "Found the correct route during reconnection attempt"); + mReconnectionStatus = RECONNECTION_STATUS_FINALIZED; + mMediaRouter.selectRoute(routeInfo); + break; + } + } + } + } + startNotificationService(); + try { + attachDataChannel(); + attachMediaChannel(); + mSessionId = sessionId; + // saving device for future retrieval; we only save the last session info + mPreferenceAccessor.saveStringToPreference(PREFS_KEY_SESSION_ID, mSessionId); + mRemoteMediaPlayer.requestStatus(mApiClient). + setResultCallback(new ResultCallback() { + + @Override + public void onResult(MediaChannelResult result) { + if (!result.getStatus().isSuccess()) { + onFailed(R.string.ccl_failed_status_request, + result.getStatus().getStatusCode()); + } + + } + }); + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onApplicationConnected(appMetadata, mSessionId, wasLaunched); + } + } catch (TransientNetworkDisconnectionException e) { + LOGE(TAG, "Failed to attach media/data channel due to network issues", e); + onFailed(R.string.ccl_failed_no_connection_trans, NO_STATUS_CODE); + } catch (NoConnectionException e) { + LOGE(TAG, "Failed to attach media/data channel due to network issues", e); + onFailed(R.string.ccl_failed_no_connection, NO_STATUS_CODE); + } + + } + + /* + * (non-Javadoc) + * @see com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager + * #onConnectivityRecovered() + */ + @Override + public void onConnectivityRecovered() { + reattachMediaChannel(); + reattachDataChannel(); + super.onConnectivityRecovered(); + } + + /* + * (non-Javadoc) + * @see com.google.android.gms.cast.CastClient.Listener#onApplicationStopFailed (int) + */ + @Override + public void onApplicationStopFailed(int errorCode) { + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onApplicationStopFailed(errorCode); + } + } + + @Override + public void onApplicationConnectionFailed(int errorCode) { + LOGD(TAG, "onApplicationConnectionFailed() reached with errorCode: " + errorCode); + mApplicationErrorCode = errorCode; + if (mReconnectionStatus == RECONNECTION_STATUS_IN_PROGRESS) { + if (errorCode == CastStatusCodes.APPLICATION_NOT_RUNNING) { + // while trying to re-establish session, we found out that the app is not running + // so we need to disconnect + mReconnectionStatus = RECONNECTION_STATUS_INACTIVE; + onDeviceSelected(null); + } + } else { + for (VideoCastConsumer consumer : mVideoConsumers) { + consumer.onApplicationConnectionFailed(errorCode); + } + onDeviceSelected(null); + if (mMediaRouter != null) { + LOGD(TAG, "onApplicationConnectionFailed(): Setting route to default"); + mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute()); + } + } + } + + /** + * Loads a media. For this to succeed, you need to have successfully launched the application. + * + * @param media The media to be loaded + * @param autoPlay If true, playback starts after load + * @param position Where to start the playback (only used if autoPlay is true. + * Units is milliseconds. + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public void loadMedia(MediaInfo media, boolean autoPlay, int position) + throws TransientNetworkDisconnectionException, NoConnectionException { + loadMedia(media, autoPlay, position, null); + } + + /** + * Loads a media. For this to succeed, you need to have successfully launched the application. + * + * @param media The media to be loaded + * @param autoPlay If true, playback starts after load + * @param position Where to start the playback (only used if autoPlay is true). + * Units is milliseconds. + * @param customData Optional {@link JSONObject} data to be passed to the cast device + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public void loadMedia(MediaInfo media, boolean autoPlay, int position, JSONObject customData) + throws TransientNetworkDisconnectionException, NoConnectionException { + loadMedia(media, null, autoPlay, position, customData); + } + + /** + * Loads a media. For this to succeed, you need to have successfully launched the application. + * + * @param media The media to be loaded + * @param activeTracks An array containing the list of track IDs to be set active for this + * media upon a successful load + * @param autoPlay If true, playback starts after load + * @param position Where to start the playback (only used if autoPlay is true). + * Units is milliseconds. + * @param customData Optional {@link JSONObject} data to be passed to the cast device + * @throws NoConnectionException + * @throws TransientNetworkDisconnectionException + */ + public void loadMedia(MediaInfo media, final long[] activeTracks, boolean autoPlay, + int position, JSONObject customData) + throws TransientNetworkDisconnectionException, NoConnectionException { + LOGD(TAG, "loadMedia"); + checkConnectivity(); + if (media == null) { + return; + } + if (mRemoteMediaPlayer == null) { + LOGE(TAG, "Trying to load a video with no active media session"); + throw new NoConnectionException(); + } + + mRemoteMediaPlayer.load(mApiClient, media, autoPlay, position, activeTracks, customData) + .setResultCallback(new ResultCallback() { + + @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 + + 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 + + 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 + + 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 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +