diff options
11 files changed, 379 insertions, 50 deletions
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index c11a99d231d5..fc2dfb833a8f 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -23,6 +23,8 @@ import android.annotation.TestApi;  import android.content.Context;  import android.os.Binder;  import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor;  import android.os.RemoteException;  import android.os.ServiceManager;  import android.telephony.Annotation.CallState; diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 8a1fd62c0201..c67fca59fe97 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -870,6 +870,94 @@ public class ViewDebug {          return null;      } +    private static class StreamingPictureCallbackHandler implements AutoCloseable, +            HardwareRenderer.PictureCapturedCallback, Runnable { +        private final HardwareRenderer mRenderer; +        private final Callable<OutputStream> mCallback; +        private final Executor mExecutor; +        private final ReentrantLock mLock = new ReentrantLock(false); +        private final ArrayDeque<byte[]> mQueue = new ArrayDeque<>(3); +        private final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream(); +        private boolean mStopListening; +        private Thread mRenderThread; + +        private StreamingPictureCallbackHandler(HardwareRenderer renderer, +                Callable<OutputStream> callback, Executor executor) { +            mRenderer = renderer; +            mCallback = callback; +            mExecutor = executor; +            mRenderer.setPictureCaptureCallback(this); +        } + +        @Override +        public void close() { +            mLock.lock(); +            mStopListening = true; +            mLock.unlock(); +            mRenderer.setPictureCaptureCallback(null); +        } + +        @Override +        public void onPictureCaptured(Picture picture) { +            mLock.lock(); +            if (mStopListening) { +                mLock.unlock(); +                mRenderer.setPictureCaptureCallback(null); +                return; +            } +            if (mRenderThread == null) { +                mRenderThread = Thread.currentThread(); +            } +            boolean needsInvoke = true; +            if (mQueue.size() == 3) { +                mQueue.removeLast(); +                needsInvoke = false; +            } +            picture.writeToStream(mByteStream); +            mQueue.add(mByteStream.toByteArray()); +            mByteStream.reset(); +            mLock.unlock(); + +            if (needsInvoke) { +                mExecutor.execute(this); +            } +        } + +        @Override +        public void run() { +            mLock.lock(); +            final byte[] picture = mQueue.poll(); +            final boolean isStopped = mStopListening; +            mLock.unlock(); +            if (Thread.currentThread() == mRenderThread) { +                close(); +                throw new IllegalStateException( +                        "ViewDebug#startRenderingCommandsCapture must be given an executor that " +                        + "invokes asynchronously"); +            } +            if (isStopped) { +                return; +            } +            OutputStream stream = null; +            try { +                stream = mCallback.call(); +            } catch (Exception ex) { +                Log.w("ViewDebug", "Aborting rendering commands capture " +                        + "because callback threw exception", ex); +            } +            if (stream != null) { +                try { +                    stream.write(picture); +                } catch (IOException ex) { +                    Log.w("ViewDebug", "Aborting rendering commands capture " +                            + "due to IOException writing to output stream", ex); +                } +            } else { +                close(); +            } +        } +    } +      /**       * Begins capturing the entire rendering commands for the view tree referenced by the given       * view. The view passed may be any View in the tree as long as it is attached. That is, @@ -915,18 +1003,7 @@ public class ViewDebug {          }          final HardwareRenderer renderer = attachInfo.mThreadedRenderer;          if (renderer != null) { -            return new PictureCallbackHandler(renderer, (picture -> { -                try { -                    OutputStream stream = callback.call(); -                    if (stream != null) { -                        picture.writeToStream(stream); -                        return true; -                    } -                } catch (Exception ex) { -                    // fall through -                } -                return false; -            }), executor); +            return new StreamingPictureCallbackHandler(renderer, callback, executor);          }          return null;      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java index 4700baae8fab..18d436ff7659 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java @@ -421,7 +421,7 @@ public class NotificationGuts extends FrameLayout {      }      /** Listener for animations executed in {@link #animateClose(int, int, boolean)}. */ -    private static class AnimateCloseListener extends AnimatorListenerAdapter { +    private class AnimateCloseListener extends AnimatorListenerAdapter {          final View mView;          private final GutsContent mGutsContent; @@ -433,8 +433,10 @@ public class NotificationGuts extends FrameLayout {          @Override          public void onAnimationEnd(Animator animation) {              super.onAnimationEnd(animation); -            mView.setVisibility(View.GONE); -            mGutsContent.onFinishedClosing(); +            if (!isExposed()) { +                mView.setVisibility(View.GONE); +                mGutsContent.onFinishedClosing(); +            }          }      }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 8f7671a5dd96..719ec3215d42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -100,6 +100,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx      protected String mKeyToRemoveOnGutsClosed;      private StatusBar mStatusBar; +    private Runnable mOpenRunnable;      @Inject      public NotificationGutsManager( @@ -343,6 +344,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx      public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,              int x, int y, boolean resetMenu) {          if (mNotificationGutsExposed != null) { +            mNotificationGutsExposed.removeCallbacks(mOpenRunnable);              mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);          }          if (resetMenu) { @@ -445,7 +447,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx          // ensure that it's laid but not visible until actually laid out          guts.setVisibility(View.INVISIBLE);          // Post to ensure the the guts are properly laid out. -        guts.post(new Runnable() { +        mOpenRunnable = new Runnable() {              @Override              public void run() {                  if (row.getWindowToken() == null) { @@ -470,7 +472,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx                  mListContainer.onHeightChanged(row, true /* needsAnimation */);                  mGutsMenuItem = menuItem;              } -        }); +        }; +        guts.post(mOpenRunnable);          return true;      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index f4635d1270a8..f7b8a2e29129 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -67,6 +67,9 @@ class KeyguardLiftController constructor(      }      private fun updateListeningState() { +        if (pickupSensor == null) { +            return +        }          val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&                  !statusBarStateController.isDozing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0c47d1468a7f..68ee8bbbb69b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -62,6 +62,8 @@ import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;  import java.io.PrintWriter;  import java.util.ArrayList; +import androidx.annotation.VisibleForTesting; +  /**   * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back   * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done, @@ -161,6 +163,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb      private boolean mLastLockVisible;      private OnDismissAction mAfterKeyguardGoneAction; +    private Runnable mKeyguardGoneCancelAction;      private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();      // Dismiss action to be launched when we stop dozing or the keyguard is gone. @@ -328,10 +331,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb          return false;      } -    private void hideBouncer(boolean destroyView) { +    @VisibleForTesting +    void hideBouncer(boolean destroyView) {          if (mBouncer == null) {              return;          } +        if (mShowing) { +            // If we were showing the bouncer and then aborting, we need to also clear out any +            // potential actions unless we actually unlocked. +            mAfterKeyguardGoneAction = null; +            if (mKeyguardGoneCancelAction != null) { +                mKeyguardGoneCancelAction.run(); +                mKeyguardGoneCancelAction = null; +            } +        }          mBouncer.hide(destroyView);          cancelPendingWakeupAction();      } @@ -364,6 +377,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb                  mBouncer.showWithDismissAction(r, cancelAction);              } else {                  mAfterKeyguardGoneAction = r; +                mKeyguardGoneCancelAction = cancelAction;                  mBouncer.show(false /* resetSecuritySelection */);              }          } @@ -671,6 +685,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb              mAfterKeyguardGoneAction.onDismiss();              mAfterKeyguardGoneAction = null;          } +        mKeyguardGoneCancelAction = null;          for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) {              mAfterKeyguardGoneRunnables.get(i).run();          } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 63f653b0b303..0da0e7647707 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -221,6 +221,31 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {          verify(mStatusBar, never()).animateKeyguardUnoccluding();      } +    @Test +    public void testHiding_cancelsGoneRunnable() { +        OnDismissAction action = mock(OnDismissAction.class); +        Runnable cancelAction = mock(Runnable.class); +        mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, +                true /* afterKeyguardGone */); + +        mStatusBarKeyguardViewManager.hideBouncer(true); +        mStatusBarKeyguardViewManager.hide(0, 30); +        verify(action, never()).onDismiss(); +        verify(cancelAction).run(); +    } + +    @Test +    public void testHiding_doesntCancelWhenShowing() { +        OnDismissAction action = mock(OnDismissAction.class); +        Runnable cancelAction = mock(Runnable.class); +        mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, +                true /* afterKeyguardGone */); + +        mStatusBarKeyguardViewManager.hide(0, 30); +        verify(action).onDismiss(); +        verify(cancelAction, never()).run(); +    } +      private class TestableStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {          public TestableStatusBarKeyguardViewManager(Context context, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1c4f1e3ed5bf..2af04ae3f800 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5280,7 +5280,7 @@ public class ActivityManagerService extends IActivityManager.Stub              storageManager.commitChanges();          } catch (Exception e) {              PowerManager pm = (PowerManager) -                     mInjector.getContext().getSystemService(Context.POWER_SERVICE); +                     mContext.getSystemService(Context.POWER_SERVICE);              pm.reboot("Checkpoint commit failed");          } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index f5579761c151..766fa3ba55c1 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1363,7 +1363,7 @@ public final class ProcessList {              final int procCount = procs.size();              for (int i = 0; i < procCount; i++) {                  final int procUid = procs.keyAt(i); -                if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) { +                if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {                      // Don't use an app process or different user process for system component.                      continue;                  } diff --git a/services/core/java/com/android/server/wallpaper/GLHelper.java b/services/core/java/com/android/server/wallpaper/GLHelper.java new file mode 100644 index 000000000000..1d733f53f055 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/GLHelper.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wallpaper; + +import static android.opengl.EGL14.EGL_ALPHA_SIZE; +import static android.opengl.EGL14.EGL_BLUE_SIZE; +import static android.opengl.EGL14.EGL_CONFIG_CAVEAT; +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; +import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY; +import static android.opengl.EGL14.EGL_DEPTH_SIZE; +import static android.opengl.EGL14.EGL_GREEN_SIZE; +import static android.opengl.EGL14.EGL_HEIGHT; +import static android.opengl.EGL14.EGL_NONE; +import static android.opengl.EGL14.EGL_NO_CONTEXT; +import static android.opengl.EGL14.EGL_NO_DISPLAY; +import static android.opengl.EGL14.EGL_NO_SURFACE; +import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT; +import static android.opengl.EGL14.EGL_RED_SIZE; +import static android.opengl.EGL14.EGL_RENDERABLE_TYPE; +import static android.opengl.EGL14.EGL_STENCIL_SIZE; +import static android.opengl.EGL14.EGL_WIDTH; +import static android.opengl.EGL14.eglChooseConfig; +import static android.opengl.EGL14.eglCreateContext; +import static android.opengl.EGL14.eglCreatePbufferSurface; +import static android.opengl.EGL14.eglDestroyContext; +import static android.opengl.EGL14.eglDestroySurface; +import static android.opengl.EGL14.eglGetDisplay; +import static android.opengl.EGL14.eglGetError; +import static android.opengl.EGL14.eglInitialize; +import static android.opengl.EGL14.eglMakeCurrent; +import static android.opengl.EGL14.eglTerminate; +import static android.opengl.GLES20.GL_MAX_TEXTURE_SIZE; +import static android.opengl.GLES20.glGetIntegerv; + +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLUtils; +import android.os.SystemProperties; +import android.util.Log; + +class GLHelper { +    private static final String TAG = GLHelper.class.getSimpleName(); +    private static final int sMaxTextureSize; + +    static { +        int maxTextureSize = SystemProperties.getInt("sys.max_texture_size", 0); +        sMaxTextureSize = maxTextureSize > 0 ? maxTextureSize : retrieveTextureSizeFromGL(); +    } + +    private static int retrieveTextureSizeFromGL() { +        try { +            String err; + +            // Before we can retrieve info from GL, +            // we have to create EGLContext, EGLConfig and EGLDisplay first. +            // We will fail at querying info from GL once one of above failed. +            // When this happens, we will use defValue instead. +            EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); +            if (eglDisplay == null || eglDisplay == EGL_NO_DISPLAY) { +                err = "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()); +                throw new RuntimeException(err); +            } + +            if (!eglInitialize(eglDisplay, null, 0 /* majorOffset */, null, 1 /* minorOffset */)) { +                err = "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError()); +                throw new RuntimeException(err); +            } + +            EGLConfig eglConfig = null; +            int[] configsCount = new int[1]; +            EGLConfig[] configs = new EGLConfig[1]; +            int[] configSpec = new int[] { +                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +                    EGL_RED_SIZE, 8, +                    EGL_GREEN_SIZE, 8, +                    EGL_BLUE_SIZE, 8, +                    EGL_ALPHA_SIZE, 0, +                    EGL_DEPTH_SIZE, 0, +                    EGL_STENCIL_SIZE, 0, +                    EGL_CONFIG_CAVEAT, EGL_NONE, +                    EGL_NONE +            }; + +            if (!eglChooseConfig(eglDisplay, configSpec, 0 /* attrib_listOffset */, +                    configs, 0  /* configOffset */, 1 /* config_size */, +                    configsCount, 0 /* num_configOffset */)) { +                err = "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError()); +                throw new RuntimeException(err); +            } else if (configsCount[0] > 0) { +                eglConfig = configs[0]; +            } + +            if (eglConfig == null) { +                throw new RuntimeException("eglConfig not initialized!"); +            } + +            int[] attr_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; +            EGLContext eglContext = eglCreateContext( +                    eglDisplay, eglConfig, EGL_NO_CONTEXT, attr_list, 0 /* offset */); + +            if (eglContext == null || eglContext == EGL_NO_CONTEXT) { +                err = "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()); +                throw new RuntimeException(err); +            } + +            // We create a push buffer temporarily for querying info from GL. +            int[] attrs = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; +            EGLSurface eglSurface = +                    eglCreatePbufferSurface(eglDisplay, eglConfig, attrs, 0 /* offset */); +            eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); + +            // Now, we are ready to query the info from GL. +            int[] maxSize = new int[1]; +            glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0 /* offset */); + +            // We have got the info we want, release all egl resources. +            eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +            eglDestroySurface(eglDisplay, eglSurface); +            eglDestroyContext(eglDisplay, eglContext); +            eglTerminate(eglDisplay); +            return maxSize[0]; +        } catch (RuntimeException e) { +            Log.w(TAG, "Retrieve from GL failed", e); +            return Integer.MAX_VALUE; +        } +    } + +    static int getMaxTextureSize() { +        return sMaxTextureSize; +    } +} + diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b0f1e5d69be4..991c09a97bf5 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -134,6 +134,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub      private static final boolean DEBUG = false;      private static final boolean DEBUG_LIVE = true; +    // This 100MB limitation is defined in RecordingCanvas. +    private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; +      public static class Lifecycle extends SystemService {          private IWallpaperManagerService mService; @@ -572,7 +575,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub          // Only generate crop for default display.          final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY); -        Rect cropHint = new Rect(wallpaper.cropHint); +        final Rect cropHint = new Rect(wallpaper.cropHint); +        final DisplayInfo displayInfo = new DisplayInfo(); +        mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);          if (DEBUG) {              Slog.v(TAG, "Generating crop for new wallpaper(s): 0x" @@ -618,12 +623,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub              }              // scale if the crop height winds up not matching the recommended metrics -            needScale = (wpData.mHeight != cropHint.height()); +            needScale = wpData.mHeight != cropHint.height() +                    || cropHint.height() > GLHelper.getMaxTextureSize() +                    || cropHint.width() > GLHelper.getMaxTextureSize();              //make sure screen aspect ratio is preserved if width is scaled under screen size              if (needScale) { -                final DisplayInfo displayInfo = new DisplayInfo(); -                mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);                  final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();                  final int newWidth = (int) (cropHint.width() * scaleByHeight);                  if (newWidth < displayInfo.logicalWidth) { @@ -644,14 +649,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub              if (!needCrop && !needScale) {                  // Simple case:  the nominal crop fits what we want, so we take                  // the whole thing and just copy the image file directly. -                if (DEBUG) { -                    Slog.v(TAG, "Null crop of new wallpaper; copying"); + +                // TODO: It is not accurate to estimate bitmap size without decoding it, +                //  may be we can try to remove this optimized way in the future, +                //  that means, we will always go into the 'else' block. + +                // This is just a quick estimation, may be smaller than it is. +                long estimateSize = options.outWidth * options.outHeight * 4; + +                // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail. +                // Please see: RecordingCanvas#throwIfCannotDraw. +                if (estimateSize < MAX_BITMAP_SIZE) { +                    success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);                  } -                success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile); +                  if (!success) {                      wallpaper.cropFile.delete();                      // TODO: fall back to default wallpaper in this case                  } + +                if (DEBUG) { +                    Slog.v(TAG, "Null crop of new wallpaper, estimate size=" +                            + estimateSize + ", success=" + success); +                }              } else {                  // Fancy case: crop and scale.  First, we decode and scale down if appropriate.                  FileOutputStream f = null; @@ -665,49 +685,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub                      // We calculate the largest power-of-two under the actual ratio rather than                      // just let the decode take care of it because we also want to remap where the                      // cropHint rectangle lies in the decoded [super]rect. -                    final BitmapFactory.Options scaler;                      final int actualScale = cropHint.height() / wpData.mHeight;                      int scale = 1; -                    while (2*scale < actualScale) { +                    while (2 * scale <= actualScale) {                          scale *= 2;                      } -                    if (scale > 1) { -                        scaler = new BitmapFactory.Options(); -                        scaler.inSampleSize = scale; +                    options.inSampleSize = scale; +                    options.inJustDecodeBounds = false; + +                    final Rect estimateCrop = new Rect(cropHint); +                    estimateCrop.scale(1f / options.inSampleSize); +                    final float hRatio = (float) wpData.mHeight / estimateCrop.height(); +                    final int destHeight = (int) (estimateCrop.height() * hRatio); +                    final int destWidth = (int) (estimateCrop.width() * hRatio); + +                    // We estimated an invalid crop, try to adjust the cropHint to get a valid one. +                    if (destWidth > GLHelper.getMaxTextureSize()) { +                        int newHeight = (int) (wpData.mHeight / hRatio); +                        int newWidth = (int) (wpData.mWidth / hRatio); +                          if (DEBUG) { -                            Slog.v(TAG, "Downsampling cropped rect with scale " + scale); +                            Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");                          } -                    } else { -                        scaler = null; + +                        estimateCrop.set(cropHint); +                        estimateCrop.left += (cropHint.width() - newWidth) / 2; +                        estimateCrop.top += (cropHint.height() - newHeight) / 2; +                        estimateCrop.right = estimateCrop.left + newWidth; +                        estimateCrop.bottom = estimateCrop.top + newHeight; +                        cropHint.set(estimateCrop); +                        estimateCrop.scale(1f / options.inSampleSize); +                    } + +                    // We've got the safe cropHint; now we want to scale it properly to +                    // the desired rectangle. +                    // That's a height-biased operation: make it fit the hinted height. +                    final int safeHeight = (int) (estimateCrop.height() * hRatio); +                    final int safeWidth = (int) (estimateCrop.width() * hRatio); + +                    if (DEBUG) { +                        Slog.v(TAG, "Decode parameters:"); +                        Slog.v(TAG, "  cropHint=" + cropHint + ", estimateCrop=" + estimateCrop); +                        Slog.v(TAG, "  down sampling=" + options.inSampleSize +                                + ", hRatio=" + hRatio); +                        Slog.v(TAG, "  dest=" + destWidth + "x" + destHeight); +                        Slog.v(TAG, "  safe=" + safeWidth + "x" + safeHeight); +                        Slog.v(TAG, "  maxTextureSize=" + GLHelper.getMaxTextureSize());                      } -                    Bitmap cropped = decoder.decodeRegion(cropHint, scaler); + +                    Bitmap cropped = decoder.decodeRegion(cropHint, options);                      decoder.recycle();                      if (cropped == null) {                          Slog.e(TAG, "Could not decode new wallpaper");                      } else { -                        // We've got the extracted crop; now we want to scale it properly to -                        // the desired rectangle.  That's a height-biased operation: make it -                        // fit the hinted height, and accept whatever width we end up with. -                        cropHint.offsetTo(0, 0); -                        cropHint.right /= scale;    // adjust by downsampling factor -                        cropHint.bottom /= scale; -                        final float heightR = -                                ((float) wpData.mHeight) / ((float) cropHint.height()); -                        if (DEBUG) { -                            Slog.v(TAG, "scale " + heightR + ", extracting " + cropHint); -                        } -                        final int destWidth = (int)(cropHint.width() * heightR); +                        // We are safe to create final crop with safe dimensions now.                          final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped, -                                destWidth, wpData.mHeight, true); +                                safeWidth, safeHeight, true);                          if (DEBUG) {                              Slog.v(TAG, "Final extract:");                              Slog.v(TAG, "  dims: w=" + wpData.mWidth                                      + " h=" + wpData.mHeight); -                            Slog.v(TAG, "   out: w=" + finalCrop.getWidth() +                            Slog.v(TAG, "  out: w=" + finalCrop.getWidth()                                      + " h=" + finalCrop.getHeight());                          } +                        // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail. +                        // Please see: RecordingCanvas#throwIfCannotDraw. +                        if (finalCrop.getByteCount() > MAX_BITMAP_SIZE) { +                            throw new RuntimeException( +                                    "Too large bitmap, limit=" + MAX_BITMAP_SIZE); +                        } +                          f = new FileOutputStream(wallpaper.cropFile);                          bos = new BufferedOutputStream(f, 32*1024);                          finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos); @@ -1981,6 +2030,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub          if (!isWallpaperSupported(callingPackage)) {              return;          } + +        // Make sure both width and height are not larger than max texture size. +        width = Math.min(width, GLHelper.getMaxTextureSize()); +        height = Math.min(height, GLHelper.getMaxTextureSize()); +          synchronized (mLock) {              int userId = UserHandle.getCallingUserId();              WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);  |