blob: a132207ab2c31959d7e74fe9a9bd009c8c435019 [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
18import android.annotation.SuppressLint;
19import android.app.WallpaperColors;
20import android.app.WallpaperManager;
21import android.content.BroadcastReceiver;
22import android.content.ComponentCallbacks2;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Canvas;
29import android.graphics.Point;
30import android.graphics.Rect;
31import android.graphics.RectF;
Jon Miranda16ea1b12017-12-12 14:52:48 -080032import android.graphics.drawable.BitmapDrawable;
33import android.graphics.drawable.Drawable;
34import android.opengl.GLES20;
35import android.opengl.GLUtils;
36import android.os.AsyncTask;
37import android.os.Build.VERSION;
38import android.os.Build.VERSION_CODES;
39import android.os.Handler;
40import android.renderscript.Matrix4f;
41import android.service.wallpaper.WallpaperService;
42import android.support.annotation.RequiresApi;
43import android.util.Log;
44import android.view.Display;
45import android.view.MotionEvent;
46import android.view.SurfaceHolder;
47import android.view.WindowManager;
48
49import com.android.wallpaper.util.ScreenSizeCalculator;
50
Jon Miranda16ea1b12017-12-12 14:52:48 -080051import java.io.FileDescriptor;
52import java.io.FileInputStream;
53import java.io.FileNotFoundException;
54import java.io.IOException;
55import java.io.PrintWriter;
56import java.nio.ByteBuffer;
57import java.nio.ByteOrder;
58import java.nio.FloatBuffer;
59
60import javax.microedition.khronos.egl.EGL10;
61import javax.microedition.khronos.egl.EGLConfig;
62import javax.microedition.khronos.egl.EGLContext;
63import javax.microedition.khronos.egl.EGLDisplay;
64import javax.microedition.khronos.egl.EGLSurface;
65
66import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
67import 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")
76public class NoBackupImageWallpaper extends WallpaperService {
77
78 public static final String ACTION_ROTATING_WALLPAPER_CHANGED =
Jon Mirandade0b69e2018-03-20 16:03:18 -070079 ".ACTION_ROTATING_WALLPAPER_CHANGED";
Jon Miranda16ea1b12017-12-12 14:52:48 -080080 public static final String PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED =
Jon Mirandade0b69e2018-03-20 16:03:18 -070081 ".NOTIFY_ROTATING_WALLPAPER_CHANGED";
Jon Miranda16ea1b12017-12-12 14:52:48 -080082 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 Mirandade0b69e2018-03-20 16:03:18 -0700216 filter.addAction(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800217
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 Mirandade0b69e2018-03-20 16:03:18 -0700226 if (action.equals(getPackageName() + ACTION_ROTATING_WALLPAPER_CHANGED)) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800227 DrawableEngine.this.invalidateAndRedrawWallpaper();
228 }
229 }
230 };
231
Jon Mirandade0b69e2018-03-20 16:03:18 -0700232 registerReceiver(mReceiver, filter, getPackageName()
233 + PERMISSION_NOTIFY_ROTATING_WALLPAPER_CHANGED, null /* handler */);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800234 }
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 Etchebehere5a7f1dd2018-04-03 15:01:29 -0700535 Context context = isPreview() ? getApplicationContext()
536 : getApplicationContext().createDeviceProtectedStorageContext();
537 FileInputStream fileInputStream = context.openFileInput(wallpaperFilePath);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800538 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 Etchebeherec9e6ec02018-03-30 10:19:58 -0700696 c.save();
697 c.clipOutRect(left, top, right, bottom);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800698 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}