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