Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package com.android.wallpaper.module; |
| 17 | |
| 18 | import android.annotation.SuppressLint; |
| 19 | import android.app.WallpaperColors; |
| 20 | import android.app.WallpaperManager; |
| 21 | import android.content.BroadcastReceiver; |
| 22 | import android.content.ComponentCallbacks2; |
| 23 | import android.content.Context; |
| 24 | import android.content.Intent; |
| 25 | import android.content.IntentFilter; |
| 26 | import android.graphics.Bitmap; |
| 27 | import android.graphics.BitmapFactory; |
| 28 | import android.graphics.Canvas; |
| 29 | import android.graphics.Point; |
| 30 | import android.graphics.Rect; |
| 31 | import android.graphics.RectF; |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 32 | import android.graphics.drawable.BitmapDrawable; |
| 33 | import android.graphics.drawable.Drawable; |
| 34 | import android.opengl.GLES20; |
| 35 | import android.opengl.GLUtils; |
| 36 | import android.os.AsyncTask; |
| 37 | import android.os.Build.VERSION; |
| 38 | import android.os.Build.VERSION_CODES; |
| 39 | import android.os.Handler; |
| 40 | import android.renderscript.Matrix4f; |
| 41 | import android.service.wallpaper.WallpaperService; |
| 42 | import android.support.annotation.RequiresApi; |
| 43 | import android.util.Log; |
| 44 | import android.view.Display; |
| 45 | import android.view.MotionEvent; |
| 46 | import android.view.SurfaceHolder; |
| 47 | import android.view.WindowManager; |
| 48 | |
| 49 | import com.android.wallpaper.util.ScreenSizeCalculator; |
| 50 | |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 51 | import java.io.FileDescriptor; |
| 52 | import java.io.FileInputStream; |
| 53 | import java.io.FileNotFoundException; |
| 54 | import java.io.IOException; |
| 55 | import java.io.PrintWriter; |
| 56 | import java.nio.ByteBuffer; |
| 57 | import java.nio.ByteOrder; |
| 58 | import java.nio.FloatBuffer; |
| 59 | |
| 60 | import javax.microedition.khronos.egl.EGL10; |
| 61 | import javax.microedition.khronos.egl.EGLConfig; |
| 62 | import javax.microedition.khronos.egl.EGLContext; |
| 63 | import javax.microedition.khronos.egl.EGLDisplay; |
| 64 | import javax.microedition.khronos.egl.EGLSurface; |
| 65 | |
| 66 | import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; |
| 67 | import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE; |
| 68 | |
| 69 | /** |
| 70 | * Live wallpaper service which simply renders a wallpaper from internal storage. Designed as a |
| 71 | * workaround to WallpaperManager not having an allowBackup=false option on pre-N builds of Android. |
| 72 | * <p> |
| 73 | * Adapted from {@code com.android.systemui.ImageWallpaper}. |
| 74 | */ |
| 75 | @SuppressLint("ServiceCast") |
| 76 | public class NoBackupImageWallpaper extends WallpaperService { |
| 77 | |
| 78 | public static final String ACTION_ROTATING_WALLPAPER_CHANGED = |
Jon Miranda | de0b69e | 2018-03-20 16:03:18 -0700 | [diff] [blame] | 79 | ".ACTION_ROTATING_WALLPAPER_CHANGED"; |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 80 | public static final String PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED = |
Jon Miranda | de0b69e | 2018-03-20 16:03:18 -0700 | [diff] [blame] | 81 | ".NOTIFY_ROTATING_WALLPAPER_CHANGED"; |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 82 | public static final String PREVIEW_WALLPAPER_FILE_PATH = "preview_wallpaper.jpg"; |
| 83 | public static final String ROTATING_WALLPAPER_FILE_PATH = "rotating_wallpaper.jpg"; |
| 84 | |
| 85 | private static final String TAG = "NoBackupImageWallpaper"; |
| 86 | private static final String GL_LOG_TAG = "ImageWallpaperGL"; |
| 87 | private static final boolean DEBUG = false; |
| 88 | private static final boolean FIXED_SIZED_SURFACE = false; |
| 89 | |
| 90 | private final Handler mHandler = new Handler(); |
| 91 | |
| 92 | private int mOpenGlContextCounter; |
| 93 | private WallpaperManager mWallpaperManager; |
| 94 | private DrawableEngine mEngine; |
| 95 | private boolean mIsHardwareAccelerated; |
| 96 | |
| 97 | @Override |
| 98 | public void onCreate() { |
| 99 | super.onCreate(); |
| 100 | |
| 101 | mOpenGlContextCounter = 0; |
| 102 | mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE); |
| 103 | |
| 104 | // By default, use OpenGL for drawing the static wallpaper image. |
| 105 | mIsHardwareAccelerated = true; |
| 106 | } |
| 107 | |
| 108 | @Override |
| 109 | public void onTrimMemory(int level) { |
| 110 | if (mEngine != null) { |
| 111 | mEngine.trimMemory(level); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | @Override |
| 116 | public Engine onCreateEngine() { |
| 117 | mEngine = new DrawableEngine(); |
| 118 | return mEngine; |
| 119 | } |
| 120 | |
| 121 | private class DrawableEngine extends Engine { |
| 122 | static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; |
| 123 | static final int EGL_OPENGL_ES2_BIT = 4; |
| 124 | private static final String S_SIMPLE_VS = |
| 125 | "attribute vec4 position;\n" |
| 126 | + "attribute vec2 texCoords;\n" |
| 127 | + "varying vec2 outTexCoords;\n" |
| 128 | + "uniform mat4 projection;\n" |
| 129 | + "\nvoid main(void) {\n" |
| 130 | + " outTexCoords = texCoords;\n" |
| 131 | + " gl_Position = projection * position;\n" |
| 132 | + "}\n\n"; |
| 133 | private static final String S_SIMPLE_FS = |
| 134 | "precision mediump float;\n\n" |
| 135 | + "varying vec2 outTexCoords;\n" |
| 136 | + "uniform sampler2D texture;\n" |
| 137 | + "\nvoid main(void) {\n" |
| 138 | + " gl_FragColor = texture2D(texture, outTexCoords);\n" |
| 139 | + "}\n\n"; |
| 140 | private static final int FLOAT_SIZE_BYTES = 4; |
| 141 | private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; |
| 142 | private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; |
| 143 | private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; |
| 144 | Bitmap mBackground; |
| 145 | WallpaperColors mCachedWallpaperColors; |
| 146 | int mBackgroundWidth = -1, mBackgroundHeight = -1; |
| 147 | int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; |
| 148 | int mLastRotation = -1; |
| 149 | float mXOffset = 0.5f; |
| 150 | float mYOffset = 0.5f; |
| 151 | float mScale = 1f; |
| 152 | boolean mVisible = true; |
| 153 | boolean mOffsetsChanged; |
| 154 | int mLastXTranslation; |
| 155 | int mLastYTranslation; |
| 156 | private Display mDefaultDisplay; |
| 157 | private EGL10 mEgl; |
| 158 | private EGLDisplay mEglDisplay; |
| 159 | private EGLConfig mEglConfig; |
| 160 | private EGLContext mEglContext; |
| 161 | private EGLSurface mEglSurface; |
| 162 | private int mTexture; |
| 163 | private int mProgram; |
| 164 | private boolean mIsOpenGlTextureLoaded; |
| 165 | private int mRotationAtLastSurfaceSizeUpdate = -1; |
| 166 | private int mDisplayWidthAtLastSurfaceSizeUpdate = -1; |
| 167 | private int mDisplayHeightAtLastSurfaceSizeUpdate = -1; |
| 168 | |
| 169 | private int mLastRequestedWidth = -1; |
| 170 | private int mLastRequestedHeight = -1; |
| 171 | private AsyncTask<Void, Void, Bitmap> mLoader; |
| 172 | private boolean mNeedsDrawAfterLoadingWallpaper; |
| 173 | private boolean mSurfaceValid; |
| 174 | |
| 175 | private BroadcastReceiver mReceiver; |
| 176 | |
| 177 | public DrawableEngine() { |
| 178 | super(); |
| 179 | } |
| 180 | |
| 181 | public void trimMemory(int level) { |
| 182 | if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW |
| 183 | && mBackground != null) { |
| 184 | if (DEBUG) { |
| 185 | Log.d(TAG, "trimMemory"); |
| 186 | } |
| 187 | mBackground.recycle(); |
| 188 | mBackground = null; |
| 189 | mBackgroundWidth = -1; |
| 190 | mBackgroundHeight = -1; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | @Override |
| 195 | public void onCreate(SurfaceHolder surfaceHolder) { |
| 196 | if (DEBUG) { |
| 197 | Log.d(TAG, "onCreate"); |
| 198 | } |
| 199 | |
| 200 | super.onCreate(surfaceHolder); |
| 201 | |
| 202 | mIsOpenGlTextureLoaded = false; |
| 203 | |
| 204 | mDefaultDisplay = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) |
| 205 | .getDefaultDisplay(); |
| 206 | |
| 207 | updateSurfaceSize(surfaceHolder, mDefaultDisplay, false /* forDraw */); |
| 208 | |
| 209 | // Enable offset notifications to pan wallpaper for parallax effect. |
| 210 | setOffsetNotificationsEnabled(true); |
| 211 | |
| 212 | // If not a preview, then register a local broadcast receiver for listening to changes in the |
| 213 | // rotating wallpaper file. |
| 214 | if (!isPreview()) { |
| 215 | IntentFilter filter = new IntentFilter(); |
Jon Miranda | de0b69e | 2018-03-20 16:03:18 -0700 | [diff] [blame] | 216 | filter.addAction(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED); |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 217 | |
| 218 | mReceiver = new BroadcastReceiver() { |
| 219 | @Override |
| 220 | public void onReceive(Context context, Intent intent) { |
| 221 | if (DEBUG) { |
| 222 | Log.i(TAG, "Broadcast received with intent: " + intent); |
| 223 | } |
| 224 | |
| 225 | String action = intent.getAction(); |
Jon Miranda | de0b69e | 2018-03-20 16:03:18 -0700 | [diff] [blame] | 226 | if (action.equals(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED)) { |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 227 | DrawableEngine.this.invalidateAndRedrawWallpaper(); |
| 228 | } |
| 229 | } |
| 230 | }; |
| 231 | |
Jon Miranda | de0b69e | 2018-03-20 16:03:18 -0700 | [diff] [blame] | 232 | registerReceiver(mReceiver, filter, getPackageName() |
| 233 | + PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED, null /* handler */); |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 234 | } |
| 235 | } |
| 236 | |
| 237 | @Override |
| 238 | public void onDestroy() { |
| 239 | super.onDestroy(); |
| 240 | mBackground = null; |
| 241 | mWallpaperManager.forgetLoadedWallpaper(); |
| 242 | |
| 243 | if (!isPreview() && mReceiver != null) { |
| 244 | unregisterReceiver(mReceiver); |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | boolean updateSurfaceSize(SurfaceHolder surfaceHolder, Display display, boolean forDraw) { |
| 249 | boolean hasWallpaper = true; |
| 250 | Point displaySize = ScreenSizeCalculator.getInstance().getScreenSize(display); |
| 251 | |
| 252 | // Load background image dimensions, if we haven't saved them yet |
| 253 | if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) { |
| 254 | // Need to load the image to get dimensions |
| 255 | loadWallpaper(forDraw); |
| 256 | if (DEBUG) { |
| 257 | Log.d(TAG, "Reloading, redoing updateSurfaceSize later."); |
| 258 | } |
| 259 | hasWallpaper = false; |
| 260 | } |
| 261 | |
| 262 | // Force the wallpaper to cover the screen in both dimensions |
| 263 | int surfaceWidth = Math.max(displaySize.x, mBackgroundWidth); |
| 264 | int surfaceHeight = Math.max(displaySize.y, mBackgroundHeight); |
| 265 | |
| 266 | if (FIXED_SIZED_SURFACE) { |
| 267 | // Used a fixed size surface, because we are special. We can do |
| 268 | // this because we know the current design of window animations doesn't |
| 269 | // cause this to break. |
| 270 | surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); |
| 271 | mLastRequestedWidth = surfaceWidth; |
| 272 | mLastRequestedHeight = surfaceHeight; |
| 273 | } else { |
| 274 | surfaceHolder.setSizeFromLayout(); |
| 275 | } |
| 276 | return hasWallpaper; |
| 277 | } |
| 278 | |
| 279 | @Override |
| 280 | public void onVisibilityChanged(boolean visible) { |
| 281 | if (DEBUG) { |
| 282 | Log.d(TAG, "onVisibilityChanged: mVisible, visible=" + mVisible + ", " + visible); |
| 283 | } |
| 284 | |
| 285 | if (mVisible != visible) { |
| 286 | if (DEBUG) { |
| 287 | Log.d(TAG, "Visibility changed to visible=" + visible); |
| 288 | } |
| 289 | mVisible = visible; |
| 290 | drawFrame(false /* forceRedraw */); |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | @Override |
| 295 | public void onTouchEvent(MotionEvent event) { |
| 296 | super.onTouchEvent(event); |
| 297 | } |
| 298 | |
| 299 | @Override |
| 300 | public void onOffsetsChanged(float xOffset, float yOffset, |
| 301 | float xOffsetStep, float yOffsetStep, |
| 302 | int xPixels, int yPixels) { |
| 303 | if (DEBUG) { |
| 304 | Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset |
| 305 | + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep |
| 306 | + ", xPixels=" + xPixels + ", yPixels=" + yPixels); |
| 307 | } |
| 308 | |
| 309 | if (mXOffset != xOffset || mYOffset != yOffset) { |
| 310 | if (DEBUG) { |
| 311 | Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ")."); |
| 312 | } |
| 313 | mXOffset = xOffset; |
| 314 | mYOffset = yOffset; |
| 315 | mOffsetsChanged = true; |
| 316 | } |
| 317 | mHandler.post(new Runnable() { |
| 318 | @Override |
| 319 | public void run() { |
| 320 | drawFrame(false /* forceRedraw */); |
| 321 | } |
| 322 | }); |
| 323 | } |
| 324 | |
| 325 | @Override |
| 326 | public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
| 327 | if (DEBUG) { |
| 328 | Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height); |
| 329 | } |
| 330 | |
| 331 | super.onSurfaceChanged(holder, format, width, height); |
| 332 | |
| 333 | // Retrieve buffer in new size. |
| 334 | if (mEgl != null) { |
| 335 | mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); |
| 336 | } |
| 337 | drawFrame(false /* forceRedraw */); |
| 338 | } |
| 339 | |
| 340 | @Override |
| 341 | public void onSurfaceDestroyed(SurfaceHolder holder) { |
| 342 | super.onSurfaceDestroyed(holder); |
| 343 | if (DEBUG) { |
| 344 | Log.d(TAG, "onSurfaceDestroyed"); |
| 345 | } |
| 346 | mLastSurfaceWidth = mLastSurfaceHeight = -1; |
| 347 | mSurfaceValid = false; |
| 348 | |
| 349 | if (mIsHardwareAccelerated) { |
| 350 | finishGL(mTexture, mProgram); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | @Override |
| 355 | public void onSurfaceCreated(SurfaceHolder holder) { |
| 356 | super.onSurfaceCreated(holder); |
| 357 | if (DEBUG) { |
| 358 | Log.d(TAG, "onSurfaceCreated"); |
| 359 | } |
| 360 | mLastSurfaceWidth = mLastSurfaceHeight = -1; |
| 361 | mSurfaceValid = true; |
| 362 | |
| 363 | if (mIsHardwareAccelerated) { |
| 364 | if (!initGL(holder)) { |
| 365 | // Fall back to canvas drawing if initializing OpenGL failed. |
| 366 | mIsHardwareAccelerated = false; |
| 367 | mEgl = null; |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | @Override |
| 373 | public void onSurfaceRedrawNeeded(SurfaceHolder holder) { |
| 374 | if (DEBUG) { |
| 375 | Log.d(TAG, "onSurfaceRedrawNeeded"); |
| 376 | } |
| 377 | super.onSurfaceRedrawNeeded(holder); |
| 378 | |
| 379 | drawFrame(true /* forceRedraw */); |
| 380 | } |
| 381 | |
| 382 | @RequiresApi(VERSION_CODES.O_MR1) |
| 383 | @Override |
| 384 | public WallpaperColors onComputeColors() { |
| 385 | // It's OK to return null here. |
| 386 | return mCachedWallpaperColors; |
| 387 | } |
| 388 | |
| 389 | /** |
| 390 | * Invalidates the currently-drawn wallpaper image, causing the engine to reload the image from |
| 391 | * disk and draw the new wallpaper image. |
| 392 | */ |
| 393 | public void invalidateAndRedrawWallpaper() { |
| 394 | // If a wallpaper load was already in flight, cancel it and restart a load in order to decode |
| 395 | // the new image. |
| 396 | if (mLoader != null) { |
| 397 | mLoader.cancel(true /* mayInterruptIfRunning */); |
| 398 | mLoader = null; |
| 399 | } |
| 400 | |
| 401 | loadWallpaper(true /* needsDraw */); |
| 402 | } |
| 403 | |
| 404 | void drawFrame(boolean forceRedraw) { |
| 405 | if (!mSurfaceValid) { |
| 406 | return; |
| 407 | } |
| 408 | |
| 409 | Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(mDefaultDisplay); |
| 410 | int newRotation = mDefaultDisplay.getRotation(); |
| 411 | |
| 412 | // Sometimes a wallpaper is not large enough to cover the screen in one dimension. |
| 413 | // Call updateSurfaceSize -- it will only actually do the update if the dimensions |
| 414 | // should change |
| 415 | if (newRotation != mLastRotation) { |
| 416 | // Update surface size (if necessary) |
| 417 | if (!updateSurfaceSize(getSurfaceHolder(), mDefaultDisplay, true /* forDraw */)) { |
| 418 | return; |
| 419 | } |
| 420 | mRotationAtLastSurfaceSizeUpdate = newRotation; |
| 421 | mDisplayWidthAtLastSurfaceSizeUpdate = screenSize.x; |
| 422 | mDisplayHeightAtLastSurfaceSizeUpdate = screenSize.y; |
| 423 | } |
| 424 | SurfaceHolder sh = getSurfaceHolder(); |
| 425 | final Rect frame = sh.getSurfaceFrame(); |
| 426 | final int dw = frame.width(); |
| 427 | final int dh = frame.height(); |
| 428 | boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth |
| 429 | || dh != mLastSurfaceHeight; |
| 430 | |
| 431 | boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation |
| 432 | || forceRedraw; |
| 433 | if (!redrawNeeded && !mOffsetsChanged) { |
| 434 | if (DEBUG) { |
| 435 | Log.d(TAG, "Suppressed drawFrame since redraw is not needed " |
| 436 | + "and offsets have not changed."); |
| 437 | } |
| 438 | return; |
| 439 | } |
| 440 | mLastRotation = newRotation; |
| 441 | |
| 442 | // Load bitmap if its null and we're not using hardware acceleration. |
| 443 | if ((mIsHardwareAccelerated && !mIsOpenGlTextureLoaded) // Using OpenGL but texture not loaded |
| 444 | || (!mIsHardwareAccelerated && mBackground == null)) { // Draw with Canvas but no bitmap |
| 445 | if (DEBUG) { |
| 446 | Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " |
| 447 | + mBackground + ", " + ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " |
| 448 | + ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " + dw + ", " + dh); |
| 449 | } |
| 450 | loadWallpaper(true /* needDraw */); |
| 451 | if (DEBUG) { |
| 452 | Log.d(TAG, "Reloading, resuming draw later"); |
| 453 | } |
| 454 | return; |
| 455 | } |
| 456 | |
| 457 | // Center the scaled image |
| 458 | mScale = Math.max(1f, Math.max(dw / (float) mBackgroundWidth, |
| 459 | dh / (float) mBackgroundHeight)); |
| 460 | final int availw = dw - (int) (mBackgroundWidth * mScale); |
| 461 | final int availh = dh - (int) (mBackgroundHeight * mScale); |
| 462 | int xPixels = availw / 2; |
| 463 | int yPixels = availh / 2; |
| 464 | |
| 465 | // Adjust the image for xOffset/yOffset values. If window manager is handling offsets, |
| 466 | // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels |
| 467 | // will remain unchanged |
| 468 | final int availwUnscaled = dw - mBackgroundWidth; |
| 469 | final int availhUnscaled = dh - mBackgroundHeight; |
| 470 | if (availwUnscaled < 0) { |
| 471 | xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f); |
| 472 | } |
| 473 | if (availhUnscaled < 0) { |
| 474 | yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f); |
| 475 | } |
| 476 | |
| 477 | mOffsetsChanged = false; |
| 478 | if (surfaceDimensionsChanged) { |
| 479 | mLastSurfaceWidth = dw; |
| 480 | mLastSurfaceHeight = dh; |
| 481 | } |
| 482 | if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) { |
| 483 | if (DEBUG) { |
| 484 | Log.d(TAG, "Suppressed drawFrame since the image has not " |
| 485 | + "actually moved an integral number of pixels."); |
| 486 | } |
| 487 | return; |
| 488 | } |
| 489 | mLastXTranslation = xPixels; |
| 490 | mLastYTranslation = yPixels; |
| 491 | |
| 492 | if (DEBUG) { |
| 493 | Log.d(TAG, "Redrawing wallpaper"); |
| 494 | } |
| 495 | |
| 496 | if (mIsHardwareAccelerated) { |
| 497 | if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { |
| 498 | drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); |
| 499 | } else { |
| 500 | // If OpenGL drawing was successful, then we can safely discard a reference to the |
| 501 | // wallpaper bitmap to save memory (since a copy has already been loaded into an OpenGL |
| 502 | // texture). |
| 503 | mBackground = null; |
| 504 | } |
| 505 | } else { |
| 506 | drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); |
| 507 | } |
| 508 | } |
| 509 | |
| 510 | /** |
| 511 | * Loads the wallpaper on background thread and schedules updating the surface frame, |
| 512 | * and if {@param needsDraw} is set also draws a frame. |
| 513 | * <p> |
| 514 | * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to |
| 515 | * the active request). |
| 516 | * <p> |
| 517 | * If {@param needsReset} is set also clears the cache in WallpaperManager first. |
| 518 | */ |
| 519 | private void loadWallpaper(boolean needsDraw) { |
| 520 | mNeedsDrawAfterLoadingWallpaper |= needsDraw; |
| 521 | if (mLoader != null) { |
| 522 | if (DEBUG) { |
| 523 | Log.d(TAG, "Skipping loadWallpaper, already in flight "); |
| 524 | } |
| 525 | return; |
| 526 | } |
| 527 | mLoader = new AsyncTask<Void, Void, Bitmap>() { |
| 528 | @Override |
| 529 | protected Bitmap doInBackground(Void... params) { |
| 530 | Throwable exception = null; |
| 531 | try { |
| 532 | // Decode bitmap of rotating image wallpaper. |
| 533 | String wallpaperFilePath = isPreview() |
| 534 | ? PREVIEW_WALLPAPER_FILE_PATH : ROTATING_WALLPAPER_FILE_PATH; |
Santiago Etchebehere | 5a7f1dd | 2018-04-03 15:01:29 -0700 | [diff] [blame] | 535 | Context context = isPreview() ? getApplicationContext() |
| 536 | : getApplicationContext().createDeviceProtectedStorageContext(); |
| 537 | FileInputStream fileInputStream = context.openFileInput(wallpaperFilePath); |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 538 | Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream); |
| 539 | fileInputStream.close(); |
| 540 | return bitmap; |
| 541 | } catch (RuntimeException | FileNotFoundException | OutOfMemoryError e) { |
| 542 | Log.i(TAG, "couldn't decode stream: ", e); |
| 543 | exception = e; |
| 544 | } catch (IOException e) { |
| 545 | Log.i(TAG, "couldn't close stream: ", e); |
| 546 | exception = e; |
| 547 | } |
| 548 | |
| 549 | if (isCancelled()) { |
| 550 | return null; |
| 551 | } |
| 552 | |
| 553 | if (exception != null) { |
| 554 | // Note that if we do fail at this, and the default wallpaper can't |
| 555 | // be loaded, we will go into a cycle. Don't do a build where the |
| 556 | // default wallpaper can't be loaded. |
| 557 | Log.w(TAG, "Unable to load wallpaper!", exception); |
| 558 | try { |
| 559 | return ((BitmapDrawable) getFallbackDrawable()).getBitmap(); |
| 560 | } catch (OutOfMemoryError ex) { |
| 561 | // now we're really screwed. |
| 562 | Log.w(TAG, "Unable reset to default wallpaper!", ex); |
| 563 | } |
| 564 | |
| 565 | if (isCancelled()) { |
| 566 | return null; |
| 567 | } |
| 568 | } |
| 569 | return null; |
| 570 | } |
| 571 | |
| 572 | @Override |
| 573 | protected void onPostExecute(Bitmap b) { |
| 574 | mBackground = null; |
| 575 | mBackgroundWidth = -1; |
| 576 | mBackgroundHeight = -1; |
| 577 | |
| 578 | if (b != null) { |
| 579 | mBackground = b; |
| 580 | mBackgroundWidth = mBackground.getWidth(); |
| 581 | mBackgroundHeight = mBackground.getHeight(); |
| 582 | |
| 583 | if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) { |
| 584 | mCachedWallpaperColors = WallpaperColors.fromBitmap(mBackground); |
| 585 | notifyColorsChanged(); |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | if (DEBUG) { |
| 590 | Log.d(TAG, "Wallpaper loaded: " + mBackground); |
| 591 | } |
| 592 | updateSurfaceSize(getSurfaceHolder(), mDefaultDisplay, |
| 593 | false /* forDraw */); |
| 594 | if (mTexture != 0 && mEgl != null) { |
| 595 | deleteTexture(mTexture); |
| 596 | } |
| 597 | // If background is absent (due to an error decoding the bitmap) then don't try to load |
| 598 | // a texture. |
| 599 | if (mEgl != null && mBackground != null) { |
| 600 | mTexture = loadTexture(mBackground); |
| 601 | } |
| 602 | if (mNeedsDrawAfterLoadingWallpaper) { |
| 603 | drawFrame(true /* forceRedraw */); |
| 604 | } |
| 605 | |
| 606 | mLoader = null; |
| 607 | mNeedsDrawAfterLoadingWallpaper = false; |
| 608 | } |
| 609 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 610 | } |
| 611 | |
| 612 | private Drawable getFallbackDrawable() { |
| 613 | Drawable drawable; |
| 614 | try { |
| 615 | drawable = mWallpaperManager.getDrawable(); |
| 616 | } catch (java.lang.Exception e) { |
| 617 | // Work around Samsung bug where SecurityException is thrown if device is still using its |
| 618 | // default wallpaper, and around Android 7.0 bug where SELinux issues can cause a perfectly |
| 619 | // valid access of the current wallpaper to cause a failed Binder transaction manifest here |
| 620 | // as a RuntimeException. |
| 621 | drawable = mWallpaperManager.getBuiltInDrawable(); |
| 622 | } |
| 623 | return drawable; |
| 624 | } |
| 625 | |
| 626 | @Override |
| 627 | protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { |
| 628 | super.dump(prefix, fd, out, args); |
| 629 | |
| 630 | out.print(prefix); |
| 631 | out.println("ImageWallpaper.DrawableEngine:"); |
| 632 | out.print(prefix); |
| 633 | out.print(" mBackground="); |
| 634 | out.print(mBackground); |
| 635 | out.print(" mBackgroundWidth="); |
| 636 | out.print(mBackgroundWidth); |
| 637 | out.print(" mBackgroundHeight="); |
| 638 | out.println(mBackgroundHeight); |
| 639 | |
| 640 | out.print(prefix); |
| 641 | out.print(" mLastRotation="); |
| 642 | out.print(mLastRotation); |
| 643 | out.print(" mLastSurfaceWidth="); |
| 644 | out.print(mLastSurfaceWidth); |
| 645 | out.print(" mLastSurfaceHeight="); |
| 646 | out.println(mLastSurfaceHeight); |
| 647 | |
| 648 | out.print(prefix); |
| 649 | out.print(" mXOffset="); |
| 650 | out.print(mXOffset); |
| 651 | out.print(" mYOffset="); |
| 652 | out.println(mYOffset); |
| 653 | |
| 654 | out.print(prefix); |
| 655 | out.print(" mVisible="); |
| 656 | out.print(mVisible); |
| 657 | out.print(" mOffsetsChanged="); |
| 658 | out.println(mOffsetsChanged); |
| 659 | |
| 660 | out.print(prefix); |
| 661 | out.print(" mLastXTranslation="); |
| 662 | out.print(mLastXTranslation); |
| 663 | out.print(" mLastYTranslation="); |
| 664 | out.print(mLastYTranslation); |
| 665 | out.print(" mScale="); |
| 666 | out.println(mScale); |
| 667 | |
| 668 | out.print(prefix); |
| 669 | out.print(" mLastRequestedWidth="); |
| 670 | out.print(mLastRequestedWidth); |
| 671 | out.print(" mLastRequestedHeight="); |
| 672 | out.println(mLastRequestedHeight); |
| 673 | |
| 674 | out.print(prefix); |
| 675 | out.println(" DisplayInfo at last updateSurfaceSize:"); |
| 676 | out.print(prefix); |
| 677 | out.print(" rotation="); |
| 678 | out.print(mRotationAtLastSurfaceSizeUpdate); |
| 679 | out.print(" width="); |
| 680 | out.print(mDisplayWidthAtLastSurfaceSizeUpdate); |
| 681 | out.print(" height="); |
| 682 | out.println(mDisplayHeightAtLastSurfaceSizeUpdate); |
| 683 | } |
| 684 | |
| 685 | private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) { |
| 686 | Canvas c = sh.lockCanvas(); |
| 687 | if (c != null) { |
| 688 | try { |
| 689 | if (DEBUG) { |
| 690 | Log.d(TAG, "Redrawing: left=" + left + ", top=" + top); |
| 691 | } |
| 692 | |
| 693 | final float right = left + mBackgroundWidth * mScale; |
| 694 | final float bottom = top + mBackgroundHeight * mScale; |
| 695 | if (w < 0 || h < 0) { |
Santiago Etchebehere | c9e6ec0 | 2018-03-30 10:19:58 -0700 | [diff] [blame] | 696 | c.save(); |
| 697 | c.clipOutRect(left, top, right, bottom); |
Jon Miranda | 16ea1b1 | 2017-12-12 14:52:48 -0800 | [diff] [blame] | 698 | c.drawColor(0xff000000); |
| 699 | c.restore(); |
| 700 | } |
| 701 | if (mBackground != null) { |
| 702 | RectF dest = new RectF(left, top, right, bottom); |
| 703 | // add a filter bitmap? |
| 704 | c.drawBitmap(mBackground, null, dest, null); |
| 705 | } |
| 706 | } finally { |
| 707 | sh.unlockCanvasAndPost(c); |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { |
| 713 | |
| 714 | mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); |
| 715 | |
| 716 | final float right = left + mBackgroundWidth * mScale; |
| 717 | final float bottom = top + mBackgroundHeight * mScale; |
| 718 | |
| 719 | final Rect frame = sh.getSurfaceFrame(); |
| 720 | final Matrix4f ortho = new Matrix4f(); |
| 721 | ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f); |
| 722 | |
| 723 | final FloatBuffer triangleVertices = createMesh(left, top, right, bottom); |
| 724 | |
| 725 | final int attribPosition = GLES20.glGetAttribLocation(mProgram, "position"); |
| 726 | final int attribTexCoords = GLES20.glGetAttribLocation(mProgram, "texCoords"); |
| 727 | final int uniformTexture = GLES20.glGetUniformLocation(mProgram, "texture"); |
| 728 | final int uniformProjection = GLES20.glGetUniformLocation(mProgram, "projection"); |
| 729 | |
| 730 | checkGlError(); |
| 731 | |
| 732 | GLES20.glViewport(0, 0, frame.width(), frame.height()); |
| 733 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture); |
| 734 | |
| 735 | GLES20.glUseProgram(mProgram); |
| 736 | GLES20.glEnableVertexAttribArray(attribPosition); |
| 737 | GLES20.glEnableVertexAttribArray(attribTexCoords); |
| 738 | GLES20.glUniform1i(uniformTexture, 0); |
| 739 | GLES20.glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0); |
| 740 | |
| 741 | checkGlError(); |
| 742 | |
| 743 | if (w > 0 || h > 0) { |
| 744 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| 745 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| 746 | } |
| 747 | |
| 748 | // drawQuad |
| 749 | triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); |
| 750 | GLES20.glVertexAttribPointer(attribPosition, 3, GLES20.GL_FLOAT, false, |
| 751 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); |
| 752 | |
| 753 | triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); |
| 754 | GLES20.glVertexAttribPointer(attribTexCoords, 3, GLES20.GL_FLOAT, false, |
| 755 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); |
| 756 | |
| 757 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
| 758 | |
| 759 | boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); |
| 760 | checkEglError(); |
| 761 | |
| 762 | return status; |
| 763 | } |
| 764 | |
| 765 | private FloatBuffer createMesh(int left, int top, float right, float bottom) { |
| 766 | final float[] verticesData = { |
| 767 | // X, Y, Z, U, V |
| 768 | left, bottom, 0.0f, 0.0f, 1.0f, |
| 769 | right, bottom, 0.0f, 1.0f, 1.0f, |
| 770 | left, top, 0.0f, 0.0f, 0.0f, |
| 771 | right, top, 0.0f, 1.0f, 0.0f, |
| 772 | }; |
| 773 | |
| 774 | final int bytes = verticesData.length * FLOAT_SIZE_BYTES; |
| 775 | final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order( |
| 776 | ByteOrder.nativeOrder()).asFloatBuffer(); |
| 777 | triangleVertices.put(verticesData).position(0); |
| 778 | return triangleVertices; |
| 779 | } |
| 780 | |
| 781 | private int loadTexture(Bitmap bitmap) { |
| 782 | mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); |
| 783 | |
| 784 | int[] textures = new int[1]; |
| 785 | |
| 786 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); |
| 787 | GLES20.glGenTextures(1, textures, 0); |
| 788 | checkGlError(); |
| 789 | |
| 790 | int texture = textures[0]; |
| 791 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); |
| 792 | checkGlError(); |
| 793 | |
| 794 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); |
| 795 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); |
| 796 | |
| 797 | GLES20.glTexParameteri( |
| 798 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); |
| 799 | GLES20.glTexParameteri( |
| 800 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); |
| 801 | |
| 802 | GLUtils.texImage2D( |
| 803 | GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap, GLES20.GL_UNSIGNED_BYTE, 0); |
| 804 | checkGlError(); |
| 805 | |
| 806 | mIsOpenGlTextureLoaded = true; |
| 807 | |
| 808 | return texture; |
| 809 | } |
| 810 | |
| 811 | private int buildProgram(String vertex, String fragment) { |
| 812 | int vertexShader = buildShader(vertex, GLES20.GL_VERTEX_SHADER); |
| 813 | if (vertexShader == 0) { |
| 814 | return 0; |
| 815 | } |
| 816 | |
| 817 | int fragmentShader = buildShader(fragment, GLES20.GL_FRAGMENT_SHADER); |
| 818 | if (fragmentShader == 0) { |
| 819 | return 0; |
| 820 | } |
| 821 | |
| 822 | int program = GLES20.glCreateProgram(); |
| 823 | GLES20.glAttachShader(program, vertexShader); |
| 824 | GLES20.glAttachShader(program, fragmentShader); |
| 825 | GLES20.glLinkProgram(program); |
| 826 | checkGlError(); |
| 827 | |
| 828 | GLES20.glDeleteShader(vertexShader); |
| 829 | GLES20.glDeleteShader(fragmentShader); |
| 830 | |
| 831 | int[] status = new int[1]; |
| 832 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0); |
| 833 | if (status[0] != GLES20.GL_TRUE) { |
| 834 | String error = GLES20.glGetProgramInfoLog(program); |
| 835 | Log.d(GL_LOG_TAG, "Error while linking program:\n" + error); |
| 836 | GLES20.glDeleteProgram(program); |
| 837 | return 0; |
| 838 | } |
| 839 | |
| 840 | return program; |
| 841 | } |
| 842 | |
| 843 | private int buildShader(String source, int type) { |
| 844 | int shader = GLES20.glCreateShader(type); |
| 845 | |
| 846 | GLES20.glShaderSource(shader, source); |
| 847 | checkGlError(); |
| 848 | |
| 849 | GLES20.glCompileShader(shader); |
| 850 | checkGlError(); |
| 851 | |
| 852 | int[] status = new int[1]; |
| 853 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0); |
| 854 | if (status[0] != GLES20.GL_TRUE) { |
| 855 | String error = GLES20.glGetShaderInfoLog(shader); |
| 856 | Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error); |
| 857 | GLES20.glDeleteShader(shader); |
| 858 | return 0; |
| 859 | } |
| 860 | |
| 861 | return shader; |
| 862 | } |
| 863 | |
| 864 | private void checkEglError() { |
| 865 | int error = mEgl.eglGetError(); |
| 866 | if (error != EGL10.EGL_SUCCESS) { |
| 867 | Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error)); |
| 868 | } |
| 869 | } |
| 870 | |
| 871 | private void checkGlError() { |
| 872 | int error = GLES20.glGetError(); |
| 873 | if (error != GLES20.GL_NO_ERROR) { |
| 874 | Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable()); |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | private void deleteTexture(int texture) { |
| 879 | int[] textures = new int[1]; |
| 880 | textures[0] = texture; |
| 881 | |
| 882 | mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); |
| 883 | GLES20.glDeleteTextures(1, textures, 0); |
| 884 | mTexture = 0; |
| 885 | } |
| 886 | |
| 887 | private void finishGL(int texture, int program) { |
| 888 | if (mEgl == null) { |
| 889 | return; |
| 890 | } |
| 891 | |
| 892 | mOpenGlContextCounter--; |
| 893 | |
| 894 | mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); |
| 895 | deleteTexture(mTexture); |
| 896 | GLES20.glDeleteProgram(program); |
| 897 | mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| 898 | mEgl.eglDestroySurface(mEglDisplay, mEglSurface); |
| 899 | mEgl.eglDestroyContext(mEglDisplay, mEglContext); |
| 900 | if (mOpenGlContextCounter == 0) { |
| 901 | mEgl.eglTerminate(mEglDisplay); |
| 902 | } |
| 903 | |
| 904 | mEgl = null; |
| 905 | } |
| 906 | |
| 907 | private boolean initGL(SurfaceHolder surfaceHolder) { |
| 908 | mEgl = (EGL10) EGLContext.getEGL(); |
| 909 | |
| 910 | mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
| 911 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { |
| 912 | throw new RuntimeException("eglGetDisplay failed " |
| 913 | + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| 914 | } |
| 915 | |
| 916 | int[] version = new int[2]; |
| 917 | if (!mEgl.eglInitialize(mEglDisplay, version)) { |
| 918 | throw new RuntimeException("eglInitialize failed " |
| 919 | + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| 920 | } |
| 921 | |
| 922 | mOpenGlContextCounter++; |
| 923 | |
| 924 | mEglConfig = chooseEglConfig(); |
| 925 | if (mEglConfig == null) { |
| 926 | throw new RuntimeException("eglConfig not initialized"); |
| 927 | } |
| 928 | |
| 929 | mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); |
| 930 | if (mEglContext == EGL_NO_CONTEXT) { |
| 931 | throw new RuntimeException("createContext failed " |
| 932 | + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| 933 | } |
| 934 | |
| 935 | int attribs[] = { |
| 936 | EGL10.EGL_WIDTH, 1, |
| 937 | EGL10.EGL_HEIGHT, 1, |
| 938 | EGL10.EGL_NONE |
| 939 | }; |
| 940 | EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); |
| 941 | mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext); |
| 942 | |
| 943 | int[] maxSize = new int[1]; |
| 944 | Rect frame = surfaceHolder.getSurfaceFrame(); |
| 945 | GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0); |
| 946 | |
| 947 | mEgl.eglMakeCurrent( |
| 948 | mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| 949 | mEgl.eglDestroySurface(mEglDisplay, tmpSurface); |
| 950 | |
| 951 | if (frame.width() > maxSize[0] || frame.height() > maxSize[0]) { |
| 952 | mEgl.eglDestroyContext(mEglDisplay, mEglContext); |
| 953 | mEgl.eglTerminate(mEglDisplay); |
| 954 | Log.e(GL_LOG_TAG, "requested texture size " + frame.width() + "x" + frame.height() |
| 955 | + " exceeds the support maximum of " + maxSize[0] + "x" + maxSize[0]); |
| 956 | return false; |
| 957 | } |
| 958 | |
| 959 | mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); |
| 960 | if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { |
| 961 | int error = mEgl.eglGetError(); |
| 962 | if (error == EGL10.EGL_BAD_NATIVE_WINDOW || error == EGL10.EGL_BAD_ALLOC) { |
| 963 | Log.e(GL_LOG_TAG, "createWindowSurface returned " + GLUtils.getEGLErrorString(error) |
| 964 | + "."); |
| 965 | return false; |
| 966 | } |
| 967 | throw new RuntimeException("createWindowSurface failed " |
| 968 | + GLUtils.getEGLErrorString(error)); |
| 969 | } |
| 970 | |
| 971 | if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { |
| 972 | throw new RuntimeException("eglMakeCurrent failed " |
| 973 | + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| 974 | } |
| 975 | |
| 976 | mProgram = buildProgram(S_SIMPLE_VS, S_SIMPLE_FS); |
| 977 | if (mBackground != null) { |
| 978 | mTexture = loadTexture(mBackground); |
| 979 | } |
| 980 | |
| 981 | return true; |
| 982 | } |
| 983 | |
| 984 | |
| 985 | EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { |
| 986 | int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; |
| 987 | return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attribList); |
| 988 | } |
| 989 | |
| 990 | private EGLConfig chooseEglConfig() { |
| 991 | int[] configsCount = new int[1]; |
| 992 | EGLConfig[] configs = new EGLConfig[1]; |
| 993 | int[] configSpec = getConfig(); |
| 994 | if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { |
| 995 | throw new IllegalArgumentException("eglChooseConfig failed " |
| 996 | + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| 997 | } else if (configsCount[0] > 0) { |
| 998 | return configs[0]; |
| 999 | } |
| 1000 | return null; |
| 1001 | } |
| 1002 | |
| 1003 | private int[] getConfig() { |
| 1004 | return new int[]{ |
| 1005 | EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| 1006 | EGL10.EGL_RED_SIZE, 8, |
| 1007 | EGL10.EGL_GREEN_SIZE, 8, |
| 1008 | EGL10.EGL_BLUE_SIZE, 8, |
| 1009 | EGL10.EGL_ALPHA_SIZE, 0, |
| 1010 | EGL10.EGL_DEPTH_SIZE, 0, |
| 1011 | EGL10.EGL_STENCIL_SIZE, 0, |
| 1012 | EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE, |
| 1013 | EGL10.EGL_NONE |
| 1014 | }; |
| 1015 | } |
| 1016 | } |
| 1017 | } |