blob: a76d88148e87c5881c9eb90ea28c6b6d9d601e62 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
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 */
16package com.android.wallpaper.module;
17
Sunny Goyal8600a3f2018-08-15 12:48:01 -070018import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
19import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
20
Jon Miranda16ea1b12017-12-12 14:52:48 -080021import android.annotation.SuppressLint;
22import android.app.WallpaperColors;
23import android.app.WallpaperManager;
24import android.content.BroadcastReceiver;
25import android.content.ComponentCallbacks2;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.Canvas;
32import android.graphics.Point;
33import android.graphics.Rect;
34import android.graphics.RectF;
Jon Miranda16ea1b12017-12-12 14:52:48 -080035import android.graphics.drawable.BitmapDrawable;
36import android.graphics.drawable.Drawable;
37import android.opengl.GLES20;
38import android.opengl.GLUtils;
39import android.os.AsyncTask;
40import android.os.Build.VERSION;
41import android.os.Build.VERSION_CODES;
42import android.os.Handler;
43import android.renderscript.Matrix4f;
44import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080045import android.util.Log;
46import android.view.Display;
47import android.view.MotionEvent;
48import android.view.SurfaceHolder;
49import android.view.WindowManager;
50
51import com.android.wallpaper.util.ScreenSizeCalculator;
52
Jon Miranda16ea1b12017-12-12 14:52:48 -080053import java.io.FileDescriptor;
54import java.io.FileInputStream;
55import java.io.FileNotFoundException;
56import java.io.IOException;
57import java.io.PrintWriter;
58import java.nio.ByteBuffer;
59import java.nio.ByteOrder;
60import java.nio.FloatBuffer;
61
62import javax.microedition.khronos.egl.EGL10;
63import javax.microedition.khronos.egl.EGLConfig;
64import javax.microedition.khronos.egl.EGLContext;
65import javax.microedition.khronos.egl.EGLDisplay;
66import javax.microedition.khronos.egl.EGLSurface;
67
Sunny Goyal8600a3f2018-08-15 12:48:01 -070068import androidx.annotation.RequiresApi;
Jon Miranda16ea1b12017-12-12 14:52:48 -080069
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")
77public class NoBackupImageWallpaper extends WallpaperService {
78
79 public static final String ACTION_ROTATING_WALLPAPER_CHANGED =
Jon Mirandade0b69e2018-03-20 16:03:18 -070080 ".ACTION_ROTATING_WALLPAPER_CHANGED";
Jon Miranda16ea1b12017-12-12 14:52:48 -080081 public static final String PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED =
Jon Mirandade0b69e2018-03-20 16:03:18 -070082 ".NOTIFY_ROTATING_WALLPAPER_CHANGED";
Jon Miranda16ea1b12017-12-12 14:52:48 -080083 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
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
110 public void onTrimMemory(int level) {
111 if (mEngine != null) {
112 mEngine.trimMemory(level);
113 }
114 }
115
116 @Override
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
178 public DrawableEngine() {
179 super();
180 }
181
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
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();
Jon Mirandade0b69e2018-03-20 16:03:18 -0700217 filter.addAction(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800218
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();
Jon Mirandade0b69e2018-03-20 16:03:18 -0700227 if (action.equals(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED)) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800228 DrawableEngine.this.invalidateAndRedrawWallpaper();
229 }
230 }
231 };
232
Jon Mirandade0b69e2018-03-20 16:03:18 -0700233 registerReceiver(mReceiver, filter, getPackageName()
234 + PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED, null /* handler */);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800235 }
236 }
237
238 @Override
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
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
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
296 public void onTouchEvent(MotionEvent event) {
297 super.onTouchEvent(event);
298 }
299
300 @Override
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
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
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
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
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
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 */
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
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 */
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;
Santiago Etchebehere5a7f1dd2018-04-03 15:01:29 -0700536 Context context = isPreview() ? getApplicationContext()
537 : getApplicationContext().createDeviceProtectedStorageContext();
538 FileInputStream fileInputStream = context.openFileInput(wallpaperFilePath);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800539 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
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
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
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) {
Santiago Etchebeherec9e6ec02018-03-30 10:19:58 -0700697 c.save();
698 c.clipOutRect(left, top, right, bottom);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800699 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
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
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
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
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
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
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
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
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
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
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
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
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
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}