1 /*
2  * Copyright (C) 2019 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  */
16 
17 package com.example.android.multidisplay.wallpaper;
18 
19 
20 import android.app.WallpaperColors;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Paint;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.graphics.drawable.ColorDrawable;
29 import android.hardware.display.DisplayManager;
30 import android.hardware.display.DisplayManager.DisplayListener;
31 import android.os.Handler;
32 import android.service.wallpaper.WallpaperService;
33 import android.util.DisplayMetrics;
34 import android.view.Display;
35 import android.view.MotionEvent;
36 import android.view.SurfaceHolder;
37 import android.view.WindowManager;
38 import java.util.Random;
39 import com.example.android.multidisplay.R;
40 
41 public class SampleWallpaper extends WallpaperService {
42 
43     @Override
onCreateEngine()44     public Engine onCreateEngine() {
45         return new MySampleEngine();
46     }
47 
48     private class MySampleEngine extends Engine {
49         private boolean mVisible = false;
50         private DisplayMetrics mDisplayMetrics;
51         private Display mDisplay;
52         private Paint mPaint = new Paint();
53 
54         private final Handler mHandler = new Handler();
55         private final Runnable mDrawRunner = this::draw;
56 
57         private String mShowingText;
58         private final Rect mTextBounds = new Rect();
59 
60         private Bitmap mTipImage;
61 
62         private final Point mCircleShift = new Point();
63         private final Point mCirclePosition = new Point();
64         private float mCircleRadioShift;
65         private final float MaxCircleRadioShift = 6f;
66         private boolean mRadioRevert = false;
67 
68         private int mBackgroundColor = Color.BLACK;
69         private int mPaintColor = Color.WHITE;
70 
71         private boolean mCircleDirectionX = false;
72         private boolean mCircleDirectionY = false;
73 
74         @Override
onCreate(SurfaceHolder surfaceHolder)75         public void onCreate(SurfaceHolder surfaceHolder) {
76             initDisplay();
77             updateDisplay();
78             initPaint();
79             genNewShift();
80             genNewColor();
81             mHandler.post(mDrawRunner);
82         }
83 
84         @Override
onDestroy()85         public void onDestroy() {
86             final DisplayManager dm = getSystemService(DisplayManager.class);
87             if (dm != null) {
88                 dm.unregisterDisplayListener(mDisplayListener);
89             }
90         }
91 
92         @Override
onComputeColors()93         public WallpaperColors onComputeColors() {
94             super.onComputeColors();
95             ColorDrawable drawable = new ColorDrawable(mBackgroundColor);
96             drawable.setBounds(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.widthPixels);
97             return WallpaperColors.fromDrawable(drawable);
98         }
99 
100         @Override
onVisibilityChanged(boolean visible)101         public void onVisibilityChanged(boolean visible) {
102             mVisible = visible;
103             if (visible) {
104                 mHandler.post(mDrawRunner);
105             } else {
106                 mHandler.removeCallbacks(mDrawRunner);
107             }
108         }
109 
110         @Override
onTouchEvent(MotionEvent event)111         public void onTouchEvent(MotionEvent event) {
112             if (event.getAction() == MotionEvent.ACTION_DOWN) {
113                 mCirclePosition.x = (int) event.getX();
114                 mCirclePosition.y = (int) event.getY();
115                 invertCircleDirectionIfNeeded();
116             }
117             super.onTouchEvent(event);
118         }
119 
120         @Override
onSurfaceDestroyed(SurfaceHolder holder)121         public void onSurfaceDestroyed(SurfaceHolder holder) {
122             super.onSurfaceDestroyed(holder);
123             mVisible = false;
124             mHandler.removeCallbacks(mDrawRunner);
125         }
126 
draw()127         private void draw() {
128             SurfaceHolder holder = getSurfaceHolder();
129             Canvas canvas = null;
130             float centerX = (float) mDisplayMetrics.widthPixels/2;
131             float centerY = (float) mDisplayMetrics.heightPixels/2;
132 
133             updateShift();
134             invertCircleDirectionIfNeeded();
135 
136             try {
137                 canvas = holder.lockCanvas();
138                 if (canvas != null) {
139                     canvas.drawColor(mBackgroundColor);
140                     if (mTipImage != null) {
141                         canvas.drawBitmap(mTipImage, 0, 0, mPaint);
142                     }
143                     canvas.drawText(mShowingText, centerX - mTextBounds.exactCenterX(),
144                         centerY - mTextBounds.exactCenterY(), mPaint);
145 
146                     canvas.drawCircle(mCirclePosition.x, mCirclePosition.y,
147                         20.0f + mCircleRadioShift, mPaint);
148                 }
149             } finally {
150                 if (canvas != null)
151                     holder.unlockCanvasAndPost(canvas);
152             }
153             mHandler.removeCallbacks(mDrawRunner);
154             if (mVisible) {
155                 mHandler.postDelayed(mDrawRunner, 40);
156             }
157         }
158 
invertCircleDirectionIfNeeded()159         private void invertCircleDirectionIfNeeded() {
160             boolean invertX = mCirclePosition.x < 0
161                 || mCirclePosition.x > mDisplayMetrics.widthPixels;
162             boolean invertY = mCirclePosition.y < 0
163                 || mCirclePosition.y > mDisplayMetrics.heightPixels;
164 
165             if (!invertX && !invertY) return;
166 
167             if (invertX) {
168                 mCircleDirectionX = mCirclePosition.x < 0;
169             }
170             if (invertY) {
171                 mCircleDirectionY = mCirclePosition.y < 0;
172             }
173 
174             genNewShift();
175             genNewColor();
176         }
177 
178         private void updateShift() {
179             mCirclePosition.x = mCircleDirectionX
180                 ? mCirclePosition.x + mCircleShift.x
181                 : mCirclePosition.x - mCircleShift.x;
182             mCirclePosition.y = mCircleDirectionY
183                 ? mCirclePosition.y + mCircleShift.y
184                 : mCirclePosition.y - mCircleShift.y;
185 
186             mCircleRadioShift = mRadioRevert ? mCircleRadioShift + 1f : mCircleRadioShift - 1f;
187             if (Math.abs(mCircleRadioShift) > MaxCircleRadioShift) {
188                 mRadioRevert = !mRadioRevert;
189             }
190         }
191 
192         private void genNewShift() {
193             Random random = new Random();
194             mCircleShift.x = Math.abs(random.nextInt(5));
195             mCircleShift.y = Math.abs(5 - mCircleShift.x);
196         }
197 
198         private void genNewColor() {
199             final Random random = new Random();
200             int br = random.nextInt(256);
201             int bg = random.nextInt(256);
202             int bb = random.nextInt(256);
203 
204             // Keep some contrast...
205             int pg = Math.abs(bg - 128);
206             int pr = Math.abs(br - 128);
207             int pb = Math.abs(bb - 128);
208             mBackgroundColor = Color.argb(255, br, bg, bb);
209             mPaintColor = Color.argb(255, pr, pg, pb);
210             mPaint.setColor(mPaintColor);
211         }
212 
213         private void initDisplay() {
214             // If we want to get display, use getDisplayContext().getSystemService so the
215             // WindowManager is created for this context.
216             final WindowManager wm = getDisplayContext().getSystemService(WindowManager.class);
217             if (wm != null) {
218                 mDisplay = wm.getDefaultDisplay();
219             }
220             final DisplayManager dm = getSystemService(DisplayManager.class);
221             if (dm != null) {
222                 dm.registerDisplayListener(mDisplayListener, null);
223             }
224         }
225 
226         private void updateDisplay() {
227             // Use getDisplayContext() to get the context for current display.
228             mDisplayMetrics = getDisplayContext().getResources().getDisplayMetrics();
229             mCirclePosition.x = mDisplayMetrics.widthPixels/2;
230             mCirclePosition.y = mDisplayMetrics.heightPixels/2 + 60;
231 
232             mShowingText = "densityDpi= " + mDisplayMetrics.densityDpi;
233             if (mTipImage != null) {
234                 mTipImage.recycle();
235                 mTipImage = null;
236             }
237             mTipImage = BitmapFactory
238                 .decodeResource(getDisplayContext().getResources(), R.drawable.res_image);
239             mPaint.getTextBounds(mShowingText, 0, mShowingText.length(), mTextBounds);
240         }
241 
242         public MySampleEngine() {
243 
244         }
245 
246         private void initPaint() {
247             mPaint.setAntiAlias(true);
248             mPaint.setStrokeWidth(1f);
249             mPaint.setTextSize(50f);
250         }
251 
252         // Use DisplayListener to know display changed.
253         private final DisplayListener mDisplayListener = new DisplayListener() {
254             @Override
255             public void onDisplayChanged(int displayId) {
256                 if (mDisplay.getDisplayId() == displayId) {
257                     updateDisplay();
258                 }
259             }
260 
261             @Override
262             public void onDisplayRemoved(int displayId) {
263                 // handle here or wait onDestroy
264             }
265 
266             @Override
267             public void onDisplayAdded(int displayId) {
268             }
269         };
270     }
271 }
272