Provide a sample of multi-display wallpaper.

Some key events to build a wallpaper which can support
multiple display.
- The rendering or event handler of each engine must be
independent.
- Set supportsMultipleDisplays attribute to true.
- Use Engine#getDisplayContext to load resource if
needed.

Bug: 129696884
Test: launch wallpaper and check on multi display.
Change-Id: I3c60caba921e0af3d5f681ed318a543328e3375a
This commit is contained in:
wilsonshih
2019-04-18 17:25:28 +08:00
parent f4de813fcf
commit a46f0248c4
9 changed files with 310 additions and 0 deletions

View File

@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidisplay"> package="com.example.android.multidisplay">
<uses-sdk android:minSdkVersion="17" />
<application <application
android:label="@string/app_name"> android:label="@string/app_name">
<activity <activity
@@ -34,5 +36,20 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name=".wallpaper.SampleWallpaper"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService">
</action>
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper">
</meta-data>
</service>
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -23,4 +23,5 @@
<string name="add_app_shortcut">Add app shortcut</string> <string name="add_app_shortcut">Add app shortcut</string>
<string name="set_wallpaper">Set wallpaper</string> <string name="set_wallpaper">Set wallpaper</string>
<string name="new_instance">Request launch in new instance</string> <string name="new_instance">Request launch in new instance</string>
<string name="wallpaper_description">Sample multidisplay wallpaper</string>
</resources> </resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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
-->
<wallpaper
xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/wallpaper_description"
android:supportsMultipleDisplays="true"
/>

View File

@@ -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) {
}
};
}
}