diff --git a/samples/MultiDisplay/AndroidManifest.xml b/samples/MultiDisplay/AndroidManifest.xml
index 8468377ee..81aa43b9a 100644
--- a/samples/MultiDisplay/AndroidManifest.xml
+++ b/samples/MultiDisplay/AndroidManifest.xml
@@ -18,6 +18,8 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MultiDisplay/res/drawable-nodpi/res_image.png b/samples/MultiDisplay/res/drawable-nodpi/res_image.png
new file mode 100644
index 000000000..14b569d58
Binary files /dev/null and b/samples/MultiDisplay/res/drawable-nodpi/res_image.png differ
diff --git a/samples/MultiDisplay/res/drawable-sw320dp/res_image.png b/samples/MultiDisplay/res/drawable-sw320dp/res_image.png
new file mode 100644
index 000000000..26fda3f5a
Binary files /dev/null and b/samples/MultiDisplay/res/drawable-sw320dp/res_image.png differ
diff --git a/samples/MultiDisplay/res/drawable-sw480dp/res_image.png b/samples/MultiDisplay/res/drawable-sw480dp/res_image.png
new file mode 100644
index 000000000..87af2ec33
Binary files /dev/null and b/samples/MultiDisplay/res/drawable-sw480dp/res_image.png differ
diff --git a/samples/MultiDisplay/res/drawable-sw640dp/res_image.png b/samples/MultiDisplay/res/drawable-sw640dp/res_image.png
new file mode 100644
index 000000000..ea57d9662
Binary files /dev/null and b/samples/MultiDisplay/res/drawable-sw640dp/res_image.png differ
diff --git a/samples/MultiDisplay/res/drawable-sw960dp/res_image.png b/samples/MultiDisplay/res/drawable-sw960dp/res_image.png
new file mode 100644
index 000000000..db9ddb8ac
Binary files /dev/null and b/samples/MultiDisplay/res/drawable-sw960dp/res_image.png differ
diff --git a/samples/MultiDisplay/res/values/strings.xml b/samples/MultiDisplay/res/values/strings.xml
index 96eb9f56b..53996f75e 100644
--- a/samples/MultiDisplay/res/values/strings.xml
+++ b/samples/MultiDisplay/res/values/strings.xml
@@ -23,4 +23,5 @@
Add app shortcut
Set wallpaper
Request launch in new instance
+ Sample multidisplay wallpaper
diff --git a/samples/MultiDisplay/res/xml/wallpaper.xml b/samples/MultiDisplay/res/xml/wallpaper.xml
new file mode 100644
index 000000000..748fcf7ee
--- /dev/null
+++ b/samples/MultiDisplay/res/xml/wallpaper.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/samples/MultiDisplay/src/com/example/android/multidisplay/wallpaper/SampleWallpaper.java b/samples/MultiDisplay/src/com/example/android/multidisplay/wallpaper/SampleWallpaper.java
new file mode 100644
index 000000000..7bf883bbc
--- /dev/null
+++ b/samples/MultiDisplay/src/com/example/android/multidisplay/wallpaper/SampleWallpaper.java
@@ -0,0 +1,271 @@
+/*
+ * 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.example.android.multidisplay.wallpaper;
+
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.service.wallpaper.WallpaperService;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import java.util.Random;
+import com.example.android.multidisplay.R;
+
+public class SampleWallpaper extends WallpaperService {
+
+ @Override
+ public Engine onCreateEngine() {
+ return new MySampleEngine();
+ }
+
+ private class MySampleEngine extends Engine {
+ private boolean mVisible = false;
+ private DisplayMetrics mDisplayMetrics;
+ private Display mDisplay;
+ private Paint mPaint = new Paint();
+
+ private final Handler mHandler = new Handler();
+ private final Runnable mDrawRunner = this::draw;
+
+ private String mShowingText;
+ private final Rect mTextBounds = new Rect();
+
+ private Bitmap mTipImage;
+
+ private final Point mCircleShift = new Point();
+ private final Point mCirclePosition = new Point();
+ private float mCircleRadioShift;
+ private final float MaxCircleRadioShift = 6f;
+ private boolean mRadioRevert = false;
+
+ private int mBackgroundColor = Color.BLACK;
+ private int mPaintColor = Color.WHITE;
+
+ private boolean mCircleDirectionX = false;
+ private boolean mCircleDirectionY = false;
+
+ @Override
+ public void onCreate(SurfaceHolder surfaceHolder) {
+ initDisplay();
+ updateDisplay();
+ initPaint();
+ genNewShift();
+ genNewColor();
+ mHandler.post(mDrawRunner);
+ }
+
+ @Override
+ public void onDestroy() {
+ final DisplayManager dm = getSystemService(DisplayManager.class);
+ if (dm != null) {
+ dm.unregisterDisplayListener(mDisplayListener);
+ }
+ }
+
+ @Override
+ public WallpaperColors onComputeColors() {
+ super.onComputeColors();
+ ColorDrawable drawable = new ColorDrawable(mBackgroundColor);
+ drawable.setBounds(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.widthPixels);
+ return WallpaperColors.fromDrawable(drawable);
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ mVisible = visible;
+ if (visible) {
+ mHandler.post(mDrawRunner);
+ } else {
+ mHandler.removeCallbacks(mDrawRunner);
+ }
+ }
+
+ @Override
+ public void onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mCirclePosition.x = (int) event.getX();
+ mCirclePosition.y = (int) event.getY();
+ invertCircleDirectionIfNeeded();
+ }
+ super.onTouchEvent(event);
+ }
+
+ @Override
+ public void onSurfaceDestroyed(SurfaceHolder holder) {
+ super.onSurfaceDestroyed(holder);
+ mVisible = false;
+ mHandler.removeCallbacks(mDrawRunner);
+ }
+
+ private void draw() {
+ SurfaceHolder holder = getSurfaceHolder();
+ Canvas canvas = null;
+ float centerX = (float) mDisplayMetrics.widthPixels/2;
+ float centerY = (float) mDisplayMetrics.heightPixels/2;
+
+ updateShift();
+ invertCircleDirectionIfNeeded();
+
+ try {
+ canvas = holder.lockCanvas();
+ if (canvas != null) {
+ canvas.drawColor(mBackgroundColor);
+ if (mTipImage != null) {
+ canvas.drawBitmap(mTipImage, 0, 0, mPaint);
+ }
+ canvas.drawText(mShowingText, centerX - mTextBounds.exactCenterX(),
+ centerY - mTextBounds.exactCenterY(), mPaint);
+
+ canvas.drawCircle(mCirclePosition.x, mCirclePosition.y,
+ 20.0f + mCircleRadioShift, mPaint);
+ }
+ } finally {
+ if (canvas != null)
+ holder.unlockCanvasAndPost(canvas);
+ }
+ mHandler.removeCallbacks(mDrawRunner);
+ if (mVisible) {
+ mHandler.postDelayed(mDrawRunner, 40);
+ }
+ }
+
+ private void invertCircleDirectionIfNeeded() {
+ boolean invertX = mCirclePosition.x < 0
+ || mCirclePosition.x > mDisplayMetrics.widthPixels;
+ boolean invertY = mCirclePosition.y < 0
+ || mCirclePosition.y > mDisplayMetrics.heightPixels;
+
+ if (!invertX && !invertY) return;
+
+ if (invertX) {
+ mCircleDirectionX = mCirclePosition.x < 0;
+ }
+ if (invertY) {
+ mCircleDirectionY = mCirclePosition.y < 0;
+ }
+
+ genNewShift();
+ genNewColor();
+ }
+
+ private void updateShift() {
+ mCirclePosition.x = mCircleDirectionX
+ ? mCirclePosition.x + mCircleShift.x
+ : mCirclePosition.x - mCircleShift.x;
+ mCirclePosition.y = mCircleDirectionY
+ ? mCirclePosition.y + mCircleShift.y
+ : mCirclePosition.y - mCircleShift.y;
+
+ mCircleRadioShift = mRadioRevert ? mCircleRadioShift + 1f : mCircleRadioShift - 1f;
+ if (Math.abs(mCircleRadioShift) > MaxCircleRadioShift) {
+ mRadioRevert = !mRadioRevert;
+ }
+ }
+
+ private void genNewShift() {
+ Random random = new Random();
+ mCircleShift.x = Math.abs(random.nextInt(5));
+ mCircleShift.y = Math.abs(5 - mCircleShift.x);
+ }
+
+ private void genNewColor() {
+ final Random random = new Random();
+ int br = random.nextInt(256);
+ int bg = random.nextInt(256);
+ int bb = random.nextInt(256);
+
+ // Keep some contrast...
+ int pg = Math.abs(bg - 128);
+ int pr = Math.abs(br - 128);
+ int pb = Math.abs(bb - 128);
+ mBackgroundColor = Color.argb(255, br, bg, bb);
+ mPaintColor = Color.argb(255, pr, pg, pb);
+ mPaint.setColor(mPaintColor);
+ }
+
+ private void initDisplay() {
+ // If we want to get display, use getDisplayContext().getSystemService so the
+ // WindowManager is created for this context.
+ final WindowManager wm = getDisplayContext().getSystemService(WindowManager.class);
+ if (wm != null) {
+ mDisplay = wm.getDefaultDisplay();
+ }
+ final DisplayManager dm = getSystemService(DisplayManager.class);
+ if (dm != null) {
+ dm.registerDisplayListener(mDisplayListener, null);
+ }
+ }
+
+ private void updateDisplay() {
+ // Use getDisplayContext() to get the context for current display.
+ mDisplayMetrics = getDisplayContext().getResources().getDisplayMetrics();
+ mCirclePosition.x = mDisplayMetrics.widthPixels/2;
+ mCirclePosition.y = mDisplayMetrics.heightPixels/2 + 60;
+
+ mShowingText = "densityDpi= " + mDisplayMetrics.densityDpi;
+ if (mTipImage != null) {
+ mTipImage.recycle();
+ mTipImage = null;
+ }
+ mTipImage = BitmapFactory
+ .decodeResource(getDisplayContext().getResources(), R.drawable.res_image);
+ mPaint.getTextBounds(mShowingText, 0, mShowingText.length(), mTextBounds);
+ }
+
+ public MySampleEngine() {
+
+ }
+
+ private void initPaint() {
+ mPaint.setAntiAlias(true);
+ mPaint.setStrokeWidth(1f);
+ mPaint.setTextSize(50f);
+ }
+
+ // Use DisplayListener to know display changed.
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (mDisplay.getDisplayId() == displayId) {
+ updateDisplay();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ // handle here or wait onDestroy
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+ };
+ }
+}