/*
* Copyright (C) 2015 Jorge Ruesga
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ruesga.android.wallpapers.photophase;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.graphics.PointF;
import android.graphics.Rect;
import android.media.effect.EffectContext;
import android.net.Uri;
import android.opengl.GLES20;
import android.opengl.GLException;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.ruesga.android.wallpapers.photophase.cast.CastService;
import com.ruesga.android.wallpapers.photophase.cast.CastUtils;
import com.ruesga.android.wallpapers.photophase.model.Disposition;
import com.ruesga.android.wallpapers.photophase.preferences.PreferencesProvider;
import com.ruesga.android.wallpapers.photophase.preferences.PreferencesProvider.Preferences;
import com.ruesga.android.wallpapers.photophase.preferences.TouchAction;
import com.ruesga.android.wallpapers.photophase.shapes.ColorShape;
import com.ruesga.android.wallpapers.photophase.shapes.OopsShape;
import com.ruesga.android.wallpapers.photophase.textures.PhotoPhaseTextureManager;
import com.ruesga.android.wallpapers.photophase.transitions.Transition;
import com.ruesga.android.wallpapers.photophase.utils.GLESUtil;
import com.ruesga.android.wallpapers.photophase.utils.GLESUtil.GLColor;
import com.ruesga.android.wallpapers.photophase.utils.GLESUtil.GLESTextureInfo;
import java.io.File;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static com.ruesga.android.wallpapers.photophase.providers.TemporaryContentAccessProvider.createAuthorizationUri;
/**
* The EGL renderer of PhotoPhase Live Wallpaper.
*/
public class PhotoPhaseRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "PhotoPhaseRenderer";
private static final boolean DEBUG = false;
private final long mInstance;
private static long sInstances;
private final boolean mIsPreview;
private boolean mIsPaused;
private boolean mRecreateWorld;
private boolean mIsDestroyed;
private final Context mContext;
private EffectContext mEffectContext;
private final Handler mHandler;
private final GLESSurfaceDispatcher mDispatcher;
private PhotoPhaseTextureManager mTextureManager;
private final AlarmManager mAlarmManager;
private PendingIntent mRecreateDispositionPendingIntent;
private PhotoPhaseWallpaperWorld mWorld;
private ColorShape mOverlay;
private OopsShape mOopsShape;
private boolean mManualTransition;
private long mLastRunningTransition;
private long mLastTransition;
private long mLastTouchTime;
private static final long TOUCH_BARRIER_TIME = 1000L;
private int mWidth = -1;
private int mHeight = -1;
private int mStatusBarHeight = 0;
private int mMeasuredHeight = -1;
private boolean mUseWallpaperOffset;
private float mOffsetX = -1f;
private final float[] mMVPMatrix = new float[16];
private final float[] mProjMatrix = new float[16];
private final float[] mVMatrix = new float[16];
private float mMVPMatrixOffset;
private final Object mDrawing = new Object();
private boolean mRecycle;
private final Object mMediaSync = new Object();
private PendingIntent mMediaScanIntent;
private ICastService mCastService;
private boolean mCastConnecting;
private final BroadcastReceiver mSettingsChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(PreferencesProvider.ACTION_SETTINGS_CHANGED)) {
// Check what flags are been requested
boolean recreateWorld = intent.getBooleanExtra(
PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, false);
boolean redraw = intent.getBooleanExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, false);
boolean emptyTextureQueue = intent.getBooleanExtra(
PreferencesProvider.EXTRA_FLAG_EMPTY_TEXTURE_QUEUE, false);
boolean mediaReload = intent.getBooleanExtra(
PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, false);
boolean mediaIntervalChanged = intent.getBooleanExtra(
PreferencesProvider.EXTRA_FLAG_MEDIA_INTERVAL_CHANGED, false);
int dispositionInterval = intent.getIntExtra(
PreferencesProvider.EXTRA_FLAG_DISPOSITION_INTERVAL_CHANGED, -1);
// Update wallpaper offset
mUseWallpaperOffset = PreferencesProvider.Preferences.General
.isWallpaperOffset(context);
// Empty texture queue?
if (emptyTextureQueue) {
if (mTextureManager != null) {
mTextureManager.emptyTextureQueue(true);
}
}
// Media reload. Purging resources and performs a media query
if (mediaReload) {
synchronized (mMediaSync) {
if (mTextureManager != null) {
boolean userReloadRequest = intent.getBooleanExtra(
PreferencesProvider.EXTRA_ACTION_MEDIA_USER_RELOAD_REQUEST, false);
mTextureManager.reloadMedia(userReloadRequest);
scheduleOrCancelMediaScan();
}
}
}
// Media scan interval was changed. Reschedule
if (mediaIntervalChanged) {
scheduleOrCancelMediaScan();
}
// Media scan interval was changed. Reschedule
if (dispositionInterval != -1) {
scheduleDispositionRecreation();
}
// Recreate the whole world?
if (recreateWorld && mWorld != null) {
recreateWorld();
}
// Performs a redraw?
if (redraw) {
forceRedraw();
}
// Preference could be changed, should disconnect the cast service?
handleCastStatusChanged();
} else if (action.equals(CastService.ACTION_CONNECTIVITY_CHANGED)) {
// Have a valid cast connectivity?
handleCastStatusChanged();
}
}
};
private final Runnable mTransitionThread = new Runnable() {
@Override
public void run() {
// Run in GLES's thread
mDispatcher.dispatch(new Runnable() {
@Override
public void run() {
try {
if (!mIsPaused) {
// Select a new transition
mWorld.selectRandomTransition();
mLastRunningTransition = System.currentTimeMillis();
mLastTransition = System.currentTimeMillis();
// Now force continuously render while transition is applied
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
} catch (Throwable ex) {
Log.e(TAG, "Something was wrong selecting the transition", ex);
}
}
});
}
};
private final Runnable mEGLContextWatchDog = new Runnable() {
@Override
public void run() {
// Restart the wallpaper
AndroidHelper.restartWallpaper();
}
};
private final ServiceConnection mCastConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
mCastService = ICastService.Stub.asInterface(binder);
mCastConnecting = false;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mCastService = null;
boolean enabled = PreferencesProvider.Preferences.Cast.isEnabled(mContext);
boolean validNetwork = CastUtils.hasValidCastNetwork(mContext);
if (enabled && validNetwork && !mIsDestroyed) {
// Reconnect
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bindToCastService();
}
}, 5000L);
}
}
};
/**
* Constructor of <code>PhotoPhaseRenderer<code>
*
* @param ctx The current context
* @param dispatcher The GLES dispatcher
* @param isPreview Indicates if the renderer is in preview mode
*/
public PhotoPhaseRenderer(Context ctx, GLESSurfaceDispatcher dispatcher, boolean isPreview) {
super();
mContext = ctx;
mHandler = new Handler();
mDispatcher = dispatcher;
mInstance = sInstances;
mIsPreview = isPreview;
mIsPaused = true;
mRecreateWorld = false;
sInstances++;
mAlarmManager = (AlarmManager)ctx.getSystemService(Context.ALARM_SERVICE);
mUseWallpaperOffset = PreferencesProvider.Preferences.General.isWallpaperOffset(ctx);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (mInstance ^ (mInstance >>> 32));
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PhotoPhaseRenderer other = (PhotoPhaseRenderer) obj;
return mInstance == other.mInstance;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "PhotoPhaseRenderer [instance: " + mInstance + "]";
}
/**
* Method called when renderer is created
*/
public void onCreate() {
if (DEBUG) Log.d(TAG, "onCreate [" + mInstance + "]");
// Register a receiver to listen for media reload request
IntentFilter filter = new IntentFilter();
filter.addAction(PreferencesProvider.ACTION_SETTINGS_CHANGED);
filter.addAction(CastService.ACTION_CONNECTIVITY_CHANGED);
mContext.registerReceiver(mSettingsChangedReceiver, filter);
// Check whether the media scan is active
int interval = Preferences.Media.getRefreshFrequency(mContext);
if (interval != Preferences.Media.MEDIA_RELOAD_DISABLED) {
// Schedule a media scan
scheduleMediaScan(interval);
}
// Link to cast service, only if we need to do
boolean castEnabled = PreferencesProvider.Preferences.Cast.isEnabled(mContext);
boolean validNetwork = CastUtils.hasValidCastNetwork(mContext);
if (castEnabled && validNetwork) {
bindToCastService();
}
}
/**
* Method called when renderer is destroyed
*/
public void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy [" + mInstance + "]");
mIsDestroyed = true;
// Register a receiver to listen for media reload request
unbindFromCastService();
mContext.unregisterReceiver(mSettingsChangedReceiver);
recycle();
if (mEffectContext != null) {
mEffectContext.release();
}
mEffectContext = null;
mWidth = -1;
mHeight = -1;
mMeasuredHeight = -1;
}
/**
* Method called when system runs under low memory
*/
public void onLowMemory() {
if (mTextureManager != null) {
mTextureManager.emptyTextureQueue(false);
}
}
/**
* Method called when the renderer should be paused
*/
public void onPause() {
if (DEBUG) Log.d(TAG, "onPause [" + mInstance + "]");
mIsPaused = true;
mHandler.removeCallbacks(mTransitionThread);
if (mTextureManager != null) {
mTextureManager.setPause(true);
}
}
/**
* Method called when the renderer should be resumed
*/
public void onResume() {
if (DEBUG) Log.d(TAG, "onResume [" + mInstance + "]");
if (mTextureManager != null) {
mTextureManager.setPause(false);
}
mIsPaused = false;
if (mRecreateWorld) {
recreateWorld();
} else {
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
// Set a watchdog to detect EGL bad context and restart the wallpaper
if (!mIsPreview) {
mHandler.postDelayed(mEGLContextWatchDog, 15000L);
}
}
@SuppressWarnings("UnusedParameters")
public void onOffsetChanged(float x , float y) {
mOffsetX = x;
mDispatcher.requestRender();
}
/**
* Method called when the renderer should process a touch event over the screen
*
* @param x The x coordinate
* @param y The y coordinate
*/
public void onTouch(float x , float y, boolean ignoreBarrier) {
if (mWorld != null) {
// Do user action
TouchAction touchAction = Preferences.General.Touch.getTouchAction(mContext);
if (touchAction.compareTo(TouchAction.NONE) != 0) {
// Avoid to handle multiple touchs
long touchTime = System.currentTimeMillis();
long diff = touchTime - mLastTouchTime;
mLastTouchTime = touchTime;
if (!ignoreBarrier && diff < TOUCH_BARRIER_TIME) {
return;
}
// Retrieve the photo frame for its coordinates
final PhotoFrame frame = mWorld.getFrameFromCoordinates(new PointF(x, y));
if (frame == null) {
Log.w(TAG, "No frame from coordenates");
return;
}
if (!frame.getDisposition().hasFlag(Disposition.BACKGROUND_FLAG)) {
// Ignore touch
return;
}
// Apply the action
if (touchAction.compareTo(TouchAction.TRANSITION) == 0) {
if (!frame.getDisposition().hasFlag(Disposition.TRANSITION_FLAG)) {
// Ignore touch
return;
}
try {
// Select the frame with a transition
// Run in GLES's thread
mDispatcher.dispatch(new Runnable() {
@Override
public void run() {
// Select a new transition
deselectCurrentTransition();
mWorld.selectTransition(frame);
mLastRunningTransition = System.currentTimeMillis();
mManualTransition = true;
// Now force continuously render while transition is applied
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
});
} catch (NotFoundException ex) {
Log.e(TAG, "The frame not exists " + frame.getTextureInfo().path, ex);
}
} else if (touchAction.compareTo(TouchAction.OPEN) == 0) {
// Open the image
if (PreferencesProvider.Preferences.General.Touch.getTouchOpenWith(mContext)) {
// Internal
File file = getFileFromFrame(frame);
if (file != null) {
Intent intent = new Intent(mContext, PhotoViewerActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(PhotoViewerActivity.EXTRA_PHOTO, file.getAbsolutePath());
mContext.startActivity(intent);
}
} else {
// External
Uri uri = getUriFromFrame(frame);
if (uri != null) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
Uri temporaryUri = createAuthorizationUri(uri);
intent.setDataAndType(temporaryUri, "image/*");
mContext.startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.e(TAG, "Open action not found for " + frame.getTextureInfo().path, ex);
}
}
}
} else if (touchAction.compareTo(TouchAction.SHARE) == 0) {
Uri uri = getUriFromFrame(frame);
if (uri != null) {
AndroidHelper.sharePicture(mContext, uri);
}
} else if (touchAction.compareTo(TouchAction.CAST) == 0) {
// Send the current photo of the target frame to a cast device
File file = getFileFromFrame(frame);
if (file != null && mCastService != null) {
try {
mCastService.cast(file.toString());
} catch (RemoteException e) {
Log.w(TAG, "Got a remote exception while casting " + file, e);
}
}
}
}
}
}
/**
* Method that returns an Uri reference from a photo frame
*
* @param frame The photo frame
* @return Uri The image uri
*/
private static Uri getUriFromFrame(final PhotoFrame frame) {
File file = getFileFromFrame(frame);
if (file == null) {
return null;
}
return Uri.fromFile(file);
}
private static File getFileFromFrame(final PhotoFrame frame) {
// Sanity checks
GLESTextureInfo info = frame.getTextureInfo();
if (info == null) {
Log.e(TAG, "The frame has not a valid reference right now." +
"Touch action is not available.");
return null;
}
if (info.path == null || !info.path.isFile()) {
Log.e(TAG, "The image do not exists. Touch action is not available.");
return null;
}
// Return the uri from the path
return frame.getTextureInfo().path;
}
private void bindToCastService() {
// Bind to cast service
if (!mIsPreview && mCastService == null && !mCastConnecting) {
mCastConnecting = true;
try {
Intent i = new Intent(mContext, CastService.class);
boolean ret = mContext.bindService(i, mCastConnection, Context.BIND_AUTO_CREATE);
if (!ret) {
mCastConnecting = false;
}
} catch (SecurityException se) {
Log.w(TAG, "Can't bound to CastService", se);
mCastConnecting = false;
}
}
}
private void unbindFromCastService() {
if (mCastService != null) {
mCastService = null;
mContext.unbindService(mCastConnection);
}
}
/**
* Method that deselect the current transition
*/
private synchronized void deselectCurrentTransition() {
mHandler.removeCallbacks(mTransitionThread);
mWorld.deselectTransition(mMVPMatrix, mMVPMatrixOffset);
mLastRunningTransition = 0;
}
private void scheduleOrCancelMediaScan() {
// Ignored in preview mode
if (mIsPreview) {
return;
}
int interval = Preferences.Media.getRefreshFrequency(mContext);
if (interval != Preferences.Media.MEDIA_RELOAD_DISABLED) {
scheduleMediaScan(interval);
} else {
cancelMediaScan();
}
}
/**
* Method that schedules a new media scan
*
* @param interval The new interval
*/
private void scheduleMediaScan(int interval) {
// Ignored in preview mode
if (mIsPreview) {
return;
}
Intent i = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
i.putExtra(PreferencesProvider.EXTRA_FLAG_MEDIA_RELOAD, Boolean.TRUE);
mMediaScanIntent = PendingIntent.getBroadcast(
mContext, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
long milliseconds = interval * 1000L;
long nextTime = System.currentTimeMillis() + milliseconds;
mAlarmManager.set(AlarmManager.RTC, nextTime, mMediaScanIntent);
}
/**
* Method that cancels a pending media scan
*/
private void cancelMediaScan() {
if (mMediaScanIntent != null) {
mAlarmManager.cancel(mMediaScanIntent);
mMediaScanIntent = null;
}
}
/**
* Method that schedule a new recreation of the current disposition
*/
private void scheduleDispositionRecreation() {
// Ignored in preview mode
if (mIsPreview) {
return;
}
// Cancel current alarm
cancelDispositionRecreation();
// Is random disposition enabled?
if (!Preferences.Layout.isRandomDispositions(mContext)) {
return;
}
// Schedule the next recreation if interval has been configured
int interval = Preferences.Layout.getRandomDispositionsInterval(mContext);
if (interval > 0) {
// Created the intent
Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED);
intent.putExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, Boolean.TRUE);
mRecreateDispositionPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// Schedule the pending intent
long nextTime = System.currentTimeMillis() + interval;
mAlarmManager.set(AlarmManager.RTC, nextTime, mRecreateDispositionPendingIntent);
}
}
/**
* Method that cancels a pending media scan
*/
private void cancelDispositionRecreation() {
// Cancel current alarm
if (mRecreateDispositionPendingIntent != null) {
mAlarmManager.cancel(mRecreateDispositionPendingIntent);
}
}
/**
* Recreate the world
*/
private void recreateWorld() {
if (mIsPaused) {
mRecreateWorld = true;
return;
}
mRecreateWorld = false;
// Recreate the wallpaper world (under a GLES context)
mDispatcher.dispatch(new Runnable() {
@Override
public void run() {
try {
synchronized (mDrawing) {
mLastRunningTransition = 0;
mWorld.recreateWorld(mWidth, mMeasuredHeight);
}
} catch (GLException e) {
Log.e(TAG, "Cannot recreate the wallpaper world.", e);
} finally {
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
scheduleDispositionRecreation();
}
});
}
/**
* Force a redraw of the screen
*/
private void forceRedraw() {
mDispatcher.requestRender();
}
/**
* Method that destroy all the internal references
*/
private void recycle() {
if (DEBUG) Log.d(TAG, "recycle [" + mInstance + "]");
// Remove any pending handle
if (mHandler != null && mTransitionThread != null) {
mHandler.removeCallbacks(mTransitionThread);
}
// Delete the world
synchronized (mDrawing) {
mRecycle = true;
if (mWorld != null) mWorld.recycle();
if (mTextureManager != null) mTextureManager.recycle();
if (mOverlay != null) mOverlay.recycle();
if (mOopsShape != null) mOopsShape.recycle();
mWorld = null;
mTextureManager = null;
mOverlay = null;
mOopsShape = null;
}
}
/**
* {@inheritDoc}
*/
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
if (DEBUG) Log.d(TAG, "onSurfaceCreated [" + mInstance + "]");
mWidth = -1;
mHeight = -1;
mMeasuredHeight = -1;
mStatusBarHeight = 0;
mLastTransition = System.currentTimeMillis();
// We have a 2d (fake) scenario, disable all unnecessary tests. Deep are
// necessary for some 3d effects
GLES20.glDisable(GL10.GL_DITHER);
GLESUtil.glesCheckError("glDisable");
GLES20.glDisable(GL10.GL_CULL_FACE);
GLESUtil.glesCheckError("glDisable");
GLES20.glEnable(GL10.GL_DEPTH_TEST);
GLESUtil.glesCheckError("glEnable");
GLES20.glDepthMask(false);
GLESUtil.glesCheckError("glDepthMask");
GLES20.glDepthFunc(GLES20.GL_LEQUAL);
GLESUtil.glesCheckError("glDepthFunc");
// Create an effect context
if (mEffectContext != null) {
mEffectContext.release();
}
mEffectContext = EffectContext.createWithCurrentGlContext();
// Create the texture manager and recycle the old one
if (mTextureManager == null) {
// Precalculate the window size for the PhotoPhaseTextureManager. In onSurfaceChanged
// the best fixed size will be set. The disposition size is simple for a better
// performance of the internal arrays
final Configuration conf = mContext.getResources().getConfiguration();
int orientation = mContext.getResources().getConfiguration().orientation;
int w = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenWidthDp);
int h = (int) AndroidHelper.convertDpToPixel(mContext, conf.screenHeightDp);
Rect dimensions = new Rect(0, 0, w, h);
int cc = (orientation == Configuration.ORIENTATION_PORTRAIT)
? Preferences.Layout.getPortraitDisposition(mContext).size()
: Preferences.Layout.getLandscapeDisposition(mContext).size();
// Recycle the current texture manager and create a new one
recycle();
mTextureManager = new PhotoPhaseTextureManager(
mContext, mHandler, mEffectContext, mDispatcher, cc, dimensions);
} else {
mTextureManager.updateEffectContext(mEffectContext);
}
// Schedule dispositions random recreation (if need it)
scheduleDispositionRecreation();
}
/**
* {@inheritDoc}
*/
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
if (DEBUG) Log.d(TAG, "onSurfaceChanged [" + mInstance + "," + width + "x" + height + "]");
// Check if the size was changed
if (mWidth == width && mHeight == height) {
return;
}
// Save the width and height to avoid recreate the world
mWidth = width;
mHeight = height;
mStatusBarHeight = AndroidHelper.calculateStatusBarHeight(mContext);
mMeasuredHeight = mHeight + mStatusBarHeight;
// Calculate a better fixed size for the pictures
Rect dimensions = new Rect(0, 0, width, height);
Rect screenDimensions = new Rect(0, AndroidHelper.isKitKatOrGreater() ? 0 : mStatusBarHeight,
width, AndroidHelper.isKitKatOrGreater() ? height + mStatusBarHeight : height);
mTextureManager.setDimensions(dimensions);
mTextureManager.setScreenDimesions(screenDimensions);
mTextureManager.setPause(false);
// Create the wallpaper (destroy the previous)
if (mWorld != null) {
mWorld.recycle();
}
mWorld = new PhotoPhaseWallpaperWorld(mContext, mTextureManager);
// Create the overlay shape
final float[] vertex = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
mOverlay = new ColorShape(mContext, vertex, Colors.getInstance(mContext).getOverlay());
// Create the Oops shape
mOopsShape = new OopsShape(mContext);
// Set the viewport and the fustrum
GLES20.glViewport(0, AndroidHelper.isKitKatOrGreater() ? 0 : -mStatusBarHeight, mWidth,
AndroidHelper.isKitKatOrGreater() ? mHeight + mStatusBarHeight : mHeight);
GLESUtil.glesCheckError("glViewport");
Matrix.frustumM(mProjMatrix, 0, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 2.0f);
// Recreate the wallpaper world
try {
mWorld.recreateWorld(width, mMeasuredHeight);
} catch (GLException e) {
Log.e(TAG, "Cannot recreate the wallpaper world.", e);
}
// Force an immediate redraw of the screen (draw thread could be in dirty mode only)
deselectCurrentTransition();
mRecycle = false;
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
/**
* {@inheritDoc}
*/
@Override
public void onDrawFrame(GL10 glUnused) {
// Check whether we have a valid surface
if (!mDispatcher.hasValidSurface()) {
return;
}
if (mRecycle) {
return;
}
// Remove the EGL context watchdog
if (!mIsPreview) {
mHandler.removeCallbacks(mEGLContextWatchDog);
}
// Calculate wallpaper offset
int widthOffset = 0;
if (!mIsPreview && mUseWallpaperOffset && mOffsetX != -1) {
widthOffset = (int) (mWidth / 3f);
}
mMVPMatrixOffset = (!mIsPreview && mUseWallpaperOffset && mOffsetX != -1)
? -0.5f * mOffsetX : 0.0f;
// Set the projection, view and model
GLES20.glViewport(0, -mStatusBarHeight, mWidth + widthOffset, mHeight);
Matrix.setLookAtM(mVMatrix, 0, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
if (mMVPMatrixOffset != 0.0f) {
Matrix.translateM(mVMatrix, 0, mMVPMatrixOffset, 0.0f, 0.0f);
}
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
if (mTextureManager != null) {
if (mTextureManager.getStatus() == 1 && mTextureManager.isEmpty()) {
// Advise the user and stop
drawOops();
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
} else {
// Draw the background
drawBackground();
if (!mIsPaused && mWorld != null) {
// Now draw the world (all the photo frames with effects)
mWorld.draw(mMVPMatrix, mMVPMatrixOffset);
// Check if we have some pending transition or transition has
// exceed its timeout
synchronized (mDrawing) {
final int interval = Preferences.General.Transitions.getTransitionInterval(mContext);
if (mManualTransition || interval > 0) {
if (!mWorld.hasRunningTransition() || isTransitionTimeoutFired()) {
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
mManualTransition = false;
// Now start a delayed thread to generate the next effect
deselectCurrentTransition();
long diff = System.currentTimeMillis() - mLastTransition;
long delay = Math.max(200, interval - diff);
mHandler.postDelayed(mTransitionThread, delay);
}
} else {
// Just display the initial frames and never make transitions
if (!mWorld.hasRunningTransition() || isTransitionTimeoutFired()) {
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
}
} else {
if (mWorld != null) {
// Just draw the world before notify GLView to goto sleep
mWorld.draw(mMVPMatrix, mMVPMatrixOffset);
}
mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
// Draw the overlay
drawOverlay();
}
}
}
/**
* Check whether the transition has exceed the timeout
*
* @return boolean if the transition has exceed the timeout
*/
private boolean isTransitionTimeoutFired() {
long now = System.currentTimeMillis();
long diff = now - mLastRunningTransition;
return mLastRunningTransition != 0 && diff > Transition.MAX_TRANSTION_TIME;
}
/**
* Method that draws the background of the wallpaper
*/
private void drawBackground() {
GLColor bg = Colors.getInstance(mContext).getBackground();
GLES20.glClearColor(bg.r, bg.g, bg.b, bg.a);
GLESUtil.glesCheckError("glClearColor");
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLESUtil.glesCheckError("glClear");
}
/**
* Method that draws the overlay of the wallpaper
*/
private void drawOverlay() {
if (mOverlay != null) {
mOverlay.setAlpha(Preferences.General.getWallpaperDim(mContext) / 100.0f);
mOverlay.draw(mMVPMatrix);
}
}
/**
* Method that draws the oops message
*/
private void drawOops() {
if (mOopsShape != null) {
mOopsShape.draw(mMVPMatrix);
}
}
private void handleCastStatusChanged() {
boolean hasConnectivity = CastUtils.hasValidCastNetwork(mContext);
boolean castEnabled = PreferencesProvider.Preferences.Cast.isEnabled(mContext);
if (castEnabled && hasConnectivity) {
bindToCastService();
} else if (castEnabled) {
unbindFromCastService();
}
}
}