WallpaperService.java revision 0586a1b77a788a119166a37fccd909bf9ed65f23
1/* 2 * Copyright (C) 2009 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 17package android.service.wallpaper; 18 19import com.android.internal.os.HandlerCaller; 20import com.android.internal.view.BaseIWindow; 21import com.android.internal.view.BaseSurfaceHolder; 22 23import android.app.Service; 24import android.app.WallpaperManager; 25import android.content.Intent; 26import android.graphics.Rect; 27import android.os.IBinder; 28import android.os.Message; 29import android.os.RemoteException; 30import android.util.Log; 31import android.view.Gravity; 32import android.view.IWindowSession; 33import android.view.MotionEvent; 34import android.view.SurfaceHolder; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.ViewRoot; 38import android.view.WindowManager; 39import android.view.WindowManagerImpl; 40 41/** 42 * A wallpaper service is responsible for showing a live wallpaper behind 43 * applications that would like to sit on top of it. 44 * @hide Live Wallpaper 45 */ 46public abstract class WallpaperService extends Service { 47 /** 48 * The {@link Intent} that must be declared as handled by the service. 49 */ 50 public static final String SERVICE_INTERFACE = 51 "android.service.wallpaper.WallpaperService"; 52 53 static final String TAG = "WallpaperService"; 54 static final boolean DEBUG = false; 55 56 private static final int DO_ATTACH = 10; 57 private static final int DO_DETACH = 20; 58 private static final int DO_SET_DESIRED_SIZE = 30; 59 60 private static final int MSG_UPDATE_SURFACE = 10000; 61 private static final int MSG_VISIBILITY_CHANGED = 10010; 62 private static final int MSG_WALLPAPER_OFFSETS = 10020; 63 private static final int MSG_WINDOW_RESIZED = 10030; 64 private static final int MSG_TOUCH_EVENT = 10040; 65 66 /** 67 * The actual implementation of a wallpaper. A wallpaper service may 68 * have multiple instances running (for example as a real wallpaper 69 * and as a preview), each of which is represented by its own Engine 70 * instance. You must implement {@link WallpaperService#onCreateEngine()} 71 * to return your concrete Engine implementation. 72 */ 73 public class Engine { 74 IWallpaperEngineWrapper mIWallpaperEngine; 75 76 // Copies from mIWallpaperEngine. 77 HandlerCaller mCaller; 78 IWallpaperConnection mConnection; 79 IBinder mWindowToken; 80 81 boolean mInitializing = true; 82 boolean mVisible; 83 boolean mDestroyed; 84 85 // Current window state. 86 boolean mCreated; 87 boolean mIsCreating; 88 boolean mDrawingAllowed; 89 int mWidth; 90 int mHeight; 91 int mFormat; 92 int mType; 93 int mCurWidth; 94 int mCurHeight; 95 int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 96 int mCurWindowFlags = mWindowFlags; 97 boolean mDestroyReportNeeded; 98 final Rect mVisibleInsets = new Rect(); 99 final Rect mWinFrame = new Rect(); 100 final Rect mContentInsets = new Rect(); 101 102 final WindowManager.LayoutParams mLayout 103 = new WindowManager.LayoutParams(); 104 IWindowSession mSession; 105 106 final Object mLock = new Object(); 107 boolean mOffsetMessageEnqueued; 108 float mPendingXOffset; 109 float mPendingYOffset; 110 MotionEvent mPendingMove; 111 112 final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { 113 114 @Override 115 public boolean onAllowLockCanvas() { 116 return mDrawingAllowed; 117 } 118 119 @Override 120 public void onRelayoutContainer() { 121 Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE); 122 mCaller.sendMessage(msg); 123 } 124 125 @Override 126 public void onUpdateSurface() { 127 Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE); 128 mCaller.sendMessage(msg); 129 } 130 131 public boolean isCreating() { 132 return mIsCreating; 133 } 134 135 @Override 136 public void setFixedSize(int width, int height) { 137 throw new UnsupportedOperationException( 138 "Wallpapers currently only support sizing from layout"); 139 } 140 141 public void setKeepScreenOn(boolean screenOn) { 142 throw new UnsupportedOperationException( 143 "Wallpapers do not support keep screen on"); 144 } 145 146 }; 147 148 final BaseIWindow mWindow = new BaseIWindow() { 149 @Override 150 public boolean onDispatchPointer(MotionEvent event, long eventTime, 151 boolean callWhenDone) { 152 synchronized (mLock) { 153 if (event.getAction() == MotionEvent.ACTION_MOVE) { 154 if (mPendingMove != null) { 155 mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove); 156 mPendingMove.recycle(); 157 } 158 mPendingMove = event; 159 } else { 160 mPendingMove = null; 161 } 162 Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, 163 event); 164 mCaller.sendMessage(msg); 165 } 166 return false; 167 } 168 169 @Override 170 public void resized(int w, int h, Rect coveredInsets, 171 Rect visibleInsets, boolean reportDraw) { 172 Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED, 173 reportDraw ? 1 : 0); 174 mCaller.sendMessage(msg); 175 } 176 177 @Override 178 public void dispatchAppVisibility(boolean visible) { 179 // We don't do this in preview mode; we'll let the preview 180 // activity tell us when to run. 181 if (!mIWallpaperEngine.mIsPreview) { 182 Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED, 183 visible ? 1 : 0); 184 mCaller.sendMessage(msg); 185 } 186 } 187 188 @Override 189 public void dispatchWallpaperOffsets(float x, float y) { 190 synchronized (mLock) { 191 mPendingXOffset = x; 192 mPendingYOffset = y; 193 if (!mOffsetMessageEnqueued) { 194 mOffsetMessageEnqueued = true; 195 Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS); 196 mCaller.sendMessage(msg); 197 } 198 } 199 } 200 201 }; 202 203 /** 204 * Provides access to the surface in which this wallpaper is drawn. 205 */ 206 public SurfaceHolder getSurfaceHolder() { 207 return mSurfaceHolder; 208 } 209 210 /** 211 * Convenience for {@link WallpaperManager#getDesiredMinimumWidth() 212 * WallpaperManager.getDesiredMinimumWidth()}, returning the width 213 * that the system would like this wallpaper to run in. 214 */ 215 public int getDesiredMinimumWidth() { 216 return mIWallpaperEngine.mReqWidth; 217 } 218 219 /** 220 * Convenience for {@link WallpaperManager#getDesiredMinimumHeight() 221 * WallpaperManager.getDesiredMinimumHeight()}, returning the height 222 * that the system would like this wallpaper to run in. 223 */ 224 public int getDesiredMinimumHeight() { 225 return mIWallpaperEngine.mReqHeight; 226 } 227 228 /** 229 * Return whether the wallpaper is currently visible to the user, 230 * this is the last value supplied to 231 * {@link #onVisibilityChanged(boolean)}. 232 */ 233 public boolean isVisible() { 234 return mVisible; 235 } 236 237 /** 238 * Returns true if this engine is running in preview mode -- that is, 239 * it is being shown to the user before they select it as the actual 240 * wallpaper. 241 */ 242 public boolean isPreview() { 243 return mIWallpaperEngine.mIsPreview; 244 } 245 246 /** 247 * Control whether this wallpaper will receive raw touch events 248 * from the window manager as the user interacts with the window 249 * that is currently displaying the wallpaper. By default they 250 * are turned off. If enabled, the events will be received in 251 * {@link #onTouchEvent(MotionEvent)}. 252 */ 253 public void setTouchEventsEnabled(boolean enabled) { 254 mWindowFlags = enabled 255 ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) 256 : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); 257 if (mCreated) { 258 updateSurface(false, false); 259 } 260 } 261 262 /** 263 * Called once to initialize the engine. After returning, the 264 * engine's surface will be created by the framework. 265 */ 266 public void onCreate(SurfaceHolder surfaceHolder) { 267 } 268 269 /** 270 * Called right before the engine is going away. After this the 271 * surface will be destroyed and this Engine object is no longer 272 * valid. 273 */ 274 public void onDestroy() { 275 } 276 277 /** 278 * Called to inform you of the wallpaper becoming visible or 279 * hidden. <em>It is very important that a wallpaper only use 280 * CPU while it is visible.</em>. 281 */ 282 public void onVisibilityChanged(boolean visible) { 283 } 284 285 /** 286 * Called as the user performs touch-screen interaction with the 287 * window that is currently showing this wallpaper. Note that the 288 * events you receive here are driven by the actual application the 289 * user is interacting with, so if it is slow you will get viewer 290 * move events. 291 */ 292 public void onTouchEvent(MotionEvent event) { 293 } 294 295 /** 296 * Called to inform you of the wallpaper's offsets changing 297 * within its contain, corresponding to the container's 298 * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float) 299 * WallpaperManager.setWallpaperOffsets()}. 300 */ 301 public void onOffsetsChanged(float xOffset, float yOffset, 302 int xPixelOffset, int yPixelOffset) { 303 } 304 305 /** 306 * Called when an application has changed the desired virtual size of 307 * the wallpaper. 308 */ 309 public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { 310 } 311 312 /** 313 * Convenience for {@link SurfaceHolder.Callback#surfaceChanged 314 * SurfaceHolder.Callback.surfaceChanged()}. 315 */ 316 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 317 } 318 319 /** 320 * Convenience for {@link SurfaceHolder.Callback#surfaceCreated 321 * SurfaceHolder.Callback.surfaceCreated()}. 322 */ 323 public void onSurfaceCreated(SurfaceHolder holder) { 324 } 325 326 /** 327 * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed 328 * SurfaceHolder.Callback.surfaceDestroyed()}. 329 */ 330 public void onSurfaceDestroyed(SurfaceHolder holder) { 331 } 332 333 void updateSurface(boolean forceRelayout, boolean forceReport) { 334 if (mDestroyed) { 335 Log.w(TAG, "Ignoring updateSurface: destroyed"); 336 } 337 338 int myWidth = mSurfaceHolder.getRequestedWidth(); 339 if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.FILL_PARENT; 340 int myHeight = mSurfaceHolder.getRequestedHeight(); 341 if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.FILL_PARENT; 342 343 final boolean creating = !mCreated; 344 final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat(); 345 boolean sizeChanged = mWidth != myWidth || mHeight != myHeight; 346 final boolean typeChanged = mType != mSurfaceHolder.getRequestedType(); 347 final boolean flagsChanged = mCurWindowFlags != mWindowFlags; 348 if (forceRelayout || creating || formatChanged || sizeChanged 349 || typeChanged || flagsChanged) { 350 351 if (DEBUG) Log.v(TAG, "Changes: creating=" + creating 352 + " format=" + formatChanged + " size=" + sizeChanged); 353 354 try { 355 mWidth = myWidth; 356 mHeight = myHeight; 357 mFormat = mSurfaceHolder.getRequestedFormat(); 358 mType = mSurfaceHolder.getRequestedType(); 359 360 mLayout.x = 0; 361 mLayout.y = 0; 362 mLayout.width = myWidth; 363 mLayout.height = myHeight; 364 365 mLayout.format = mFormat; 366 367 mCurWindowFlags = mWindowFlags; 368 mLayout.flags = mWindowFlags 369 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 370 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 371 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 372 ; 373 374 mLayout.memoryType = mType; 375 mLayout.token = mWindowToken; 376 377 if (!mCreated) { 378 mLayout.type = mIWallpaperEngine.mWindowType; 379 mLayout.gravity = Gravity.LEFT|Gravity.TOP; 380 mLayout.setTitle(WallpaperService.this.getClass().getName()); 381 mLayout.windowAnimations = 382 com.android.internal.R.style.Animation_Wallpaper; 383 mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets); 384 } 385 386 mSurfaceHolder.mSurfaceLock.lock(); 387 mDrawingAllowed = true; 388 389 final int relayoutResult = mSession.relayout( 390 mWindow, mLayout, mWidth, mHeight, 391 View.VISIBLE, false, mWinFrame, mContentInsets, 392 mVisibleInsets, mSurfaceHolder.mSurface); 393 394 if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface 395 + ", frame=" + mWinFrame); 396 397 int w = mWinFrame.width(); 398 if (mCurWidth != w) { 399 sizeChanged = true; 400 mCurWidth = w; 401 } 402 int h = mWinFrame.height(); 403 if (mCurHeight != h) { 404 sizeChanged = true; 405 mCurHeight = h; 406 } 407 408 mSurfaceHolder.mSurfaceLock.unlock(); 409 410 try { 411 mDestroyReportNeeded = true; 412 413 SurfaceHolder.Callback callbacks[] = null; 414 synchronized (mSurfaceHolder.mCallbacks) { 415 final int N = mSurfaceHolder.mCallbacks.size(); 416 if (N > 0) { 417 callbacks = new SurfaceHolder.Callback[N]; 418 mSurfaceHolder.mCallbacks.toArray(callbacks); 419 } 420 } 421 422 if (!mCreated) { 423 mIsCreating = true; 424 if (DEBUG) Log.v(TAG, "onSurfaceCreated(" 425 + mSurfaceHolder + "): " + this); 426 onSurfaceCreated(mSurfaceHolder); 427 if (callbacks != null) { 428 for (SurfaceHolder.Callback c : callbacks) { 429 c.surfaceCreated(mSurfaceHolder); 430 } 431 } 432 } 433 if (forceReport || creating || formatChanged || sizeChanged) { 434 if (DEBUG) { 435 RuntimeException e = new RuntimeException(); 436 e.fillInStackTrace(); 437 Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating 438 + " formatChanged=" + formatChanged 439 + " sizeChanged=" + sizeChanged, e); 440 } 441 if (DEBUG) Log.v(TAG, "onSurfaceChanged(" 442 + mSurfaceHolder + ", " + mFormat 443 + ", " + mCurWidth + ", " + mCurHeight 444 + "): " + this); 445 onSurfaceChanged(mSurfaceHolder, mFormat, 446 mCurWidth, mCurHeight); 447 if (callbacks != null) { 448 for (SurfaceHolder.Callback c : callbacks) { 449 c.surfaceChanged(mSurfaceHolder, mFormat, 450 mCurWidth, mCurHeight); 451 } 452 } 453 } 454 } finally { 455 mIsCreating = false; 456 mCreated = true; 457 if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { 458 mSession.finishDrawing(mWindow); 459 } 460 } 461 } catch (RemoteException ex) { 462 } 463 if (DEBUG) Log.v( 464 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + 465 " w=" + mLayout.width + " h=" + mLayout.height); 466 } 467 } 468 469 void attach(IWallpaperEngineWrapper wrapper) { 470 if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper); 471 if (mDestroyed) { 472 return; 473 } 474 475 mIWallpaperEngine = wrapper; 476 mCaller = wrapper.mCaller; 477 mConnection = wrapper.mConnection; 478 mWindowToken = wrapper.mWindowToken; 479 mSurfaceHolder.setSizeFromLayout(); 480 mInitializing = true; 481 mSession = ViewRoot.getWindowSession(getMainLooper()); 482 mWindow.setSession(mSession); 483 484 if (DEBUG) Log.v(TAG, "onCreate(): " + this); 485 onCreate(mSurfaceHolder); 486 487 mInitializing = false; 488 updateSurface(false, false); 489 } 490 491 void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { 492 if (!mDestroyed) { 493 if (DEBUG) Log.v(TAG, "onDesiredSizeChanged(" 494 + desiredWidth + "," + desiredHeight + "): " + this); 495 onDesiredSizeChanged(desiredWidth, desiredHeight); 496 } 497 } 498 499 void doVisibilityChanged(boolean visible) { 500 if (!mDestroyed) { 501 mVisible = visible; 502 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible 503 + "): " + this); 504 onVisibilityChanged(visible); 505 } 506 } 507 508 void doOffsetsChanged() { 509 if (mDestroyed) { 510 return; 511 } 512 513 float xOffset; 514 float yOffset; 515 synchronized (mLock) { 516 xOffset = mPendingXOffset; 517 yOffset = mPendingYOffset; 518 mOffsetMessageEnqueued = false; 519 } 520 if (DEBUG) Log.v(TAG, "Offsets change in " + this 521 + ": " + xOffset + "," + yOffset); 522 final int availw = mIWallpaperEngine.mReqWidth-mCurWidth; 523 final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0; 524 final int availh = mIWallpaperEngine.mReqHeight-mCurHeight; 525 final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0; 526 onOffsetsChanged(xOffset, yOffset, xPixels, yPixels); 527 } 528 529 void detach() { 530 mDestroyed = true; 531 532 if (mVisible) { 533 mVisible = false; 534 if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this); 535 onVisibilityChanged(false); 536 } 537 538 if (mDestroyReportNeeded) { 539 mDestroyReportNeeded = false; 540 SurfaceHolder.Callback callbacks[]; 541 synchronized (mSurfaceHolder.mCallbacks) { 542 callbacks = new SurfaceHolder.Callback[ 543 mSurfaceHolder.mCallbacks.size()]; 544 mSurfaceHolder.mCallbacks.toArray(callbacks); 545 } 546 for (SurfaceHolder.Callback c : callbacks) { 547 c.surfaceDestroyed(mSurfaceHolder); 548 } 549 if (DEBUG) Log.v(TAG, "onSurfaceDestroyed(" 550 + mSurfaceHolder + "): " + this); 551 onSurfaceDestroyed(mSurfaceHolder); 552 } 553 554 if (DEBUG) Log.v(TAG, "onDestroy(): " + this); 555 onDestroy(); 556 557 if (mCreated) { 558 try { 559 mSession.remove(mWindow); 560 } catch (RemoteException e) { 561 } 562 mSurfaceHolder.mSurface.release(); 563 mCreated = false; 564 } 565 } 566 } 567 568 class IWallpaperEngineWrapper extends IWallpaperEngine.Stub 569 implements HandlerCaller.Callback { 570 private final HandlerCaller mCaller; 571 572 final IWallpaperConnection mConnection; 573 final IBinder mWindowToken; 574 final int mWindowType; 575 final boolean mIsPreview; 576 int mReqWidth; 577 int mReqHeight; 578 579 Engine mEngine; 580 581 IWallpaperEngineWrapper(WallpaperService context, 582 IWallpaperConnection conn, IBinder windowToken, 583 int windowType, boolean isPreview, int reqWidth, int reqHeight) { 584 mCaller = new HandlerCaller(context, this); 585 mConnection = conn; 586 mWindowToken = windowToken; 587 mWindowType = windowType; 588 mIsPreview = isPreview; 589 mReqWidth = reqWidth; 590 mReqHeight = reqHeight; 591 592 Message msg = mCaller.obtainMessage(DO_ATTACH); 593 mCaller.sendMessage(msg); 594 } 595 596 public void setDesiredSize(int width, int height) { 597 Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height); 598 mCaller.sendMessage(msg); 599 } 600 601 public void setVisibility(boolean visible) { 602 Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED, 603 visible ? 1 : 0); 604 mCaller.sendMessage(msg); 605 } 606 607 public void destroy() { 608 Message msg = mCaller.obtainMessage(DO_DETACH); 609 mCaller.sendMessage(msg); 610 } 611 612 public void executeMessage(Message message) { 613 switch (message.what) { 614 case DO_ATTACH: { 615 try { 616 mConnection.attachEngine(this); 617 } catch (RemoteException e) { 618 Log.w(TAG, "Wallpaper host disappeared", e); 619 return; 620 } 621 Engine engine = onCreateEngine(); 622 mEngine = engine; 623 engine.attach(this); 624 return; 625 } 626 case DO_DETACH: { 627 mEngine.detach(); 628 return; 629 } 630 case DO_SET_DESIRED_SIZE: { 631 mEngine.doDesiredSizeChanged(message.arg1, message.arg2); 632 return; 633 } 634 case MSG_UPDATE_SURFACE: 635 mEngine.updateSurface(true, false); 636 break; 637 case MSG_VISIBILITY_CHANGED: 638 if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine 639 + ": " + message.arg1); 640 mEngine.doVisibilityChanged(message.arg1 != 0); 641 break; 642 case MSG_WALLPAPER_OFFSETS: { 643 mEngine.doOffsetsChanged(); 644 } break; 645 case MSG_WINDOW_RESIZED: { 646 final boolean reportDraw = message.arg1 != 0; 647 mEngine.updateSurface(true, false); 648 if (reportDraw) { 649 try { 650 mEngine.mSession.finishDrawing(mEngine.mWindow); 651 } catch (RemoteException e) { 652 } 653 } 654 } break; 655 case MSG_TOUCH_EVENT: { 656 MotionEvent ev = (MotionEvent)message.obj; 657 synchronized (mEngine.mLock) { 658 if (mEngine.mPendingMove == ev) { 659 mEngine.mPendingMove = null; 660 } 661 } 662 mEngine.onTouchEvent(ev); 663 ev.recycle(); 664 } break; 665 default : 666 Log.w(TAG, "Unknown message type " + message.what); 667 } 668 } 669 } 670 671 /** 672 * Implements the internal {@link IWallpaperService} interface to convert 673 * incoming calls to it back to calls on an {@link WallpaperService}. 674 */ 675 class IWallpaperServiceWrapper extends IWallpaperService.Stub { 676 private final WallpaperService mTarget; 677 678 public IWallpaperServiceWrapper(WallpaperService context) { 679 mTarget = context; 680 } 681 682 public void attach(IWallpaperConnection conn, IBinder windowToken, 683 int windowType, boolean isPreview, int reqWidth, int reqHeight) { 684 new IWallpaperEngineWrapper(mTarget, conn, windowToken, 685 windowType, isPreview, reqWidth, reqHeight); 686 } 687 } 688 689 /** 690 * Implement to return the implementation of the internal accessibility 691 * service interface. Subclasses should not override. 692 */ 693 @Override 694 public final IBinder onBind(Intent intent) { 695 return new IWallpaperServiceWrapper(this); 696 } 697 698 public abstract Engine onCreateEngine(); 699} 700