WallpaperService.java revision 284ac93aa30642fda87d5c40263a1263677c21cd
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.windowAnimations = 381 com.android.internal.R.style.Animation_Wallpaper; 382 mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets); 383 } 384 385 mSurfaceHolder.mSurfaceLock.lock(); 386 mDrawingAllowed = true; 387 388 final int relayoutResult = mSession.relayout( 389 mWindow, mLayout, mWidth, mHeight, 390 View.VISIBLE, false, mWinFrame, mContentInsets, 391 mVisibleInsets, mSurfaceHolder.mSurface); 392 393 if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface 394 + ", frame=" + mWinFrame); 395 396 int w = mWinFrame.width(); 397 if (mCurWidth != w) { 398 sizeChanged = true; 399 mCurWidth = w; 400 } 401 int h = mWinFrame.height(); 402 if (mCurHeight != h) { 403 sizeChanged = true; 404 mCurHeight = h; 405 } 406 407 mSurfaceHolder.mSurfaceLock.unlock(); 408 409 try { 410 mDestroyReportNeeded = true; 411 412 SurfaceHolder.Callback callbacks[] = null; 413 synchronized (mSurfaceHolder.mCallbacks) { 414 final int N = mSurfaceHolder.mCallbacks.size(); 415 if (N > 0) { 416 callbacks = new SurfaceHolder.Callback[N]; 417 mSurfaceHolder.mCallbacks.toArray(callbacks); 418 } 419 } 420 421 if (!mCreated) { 422 mIsCreating = true; 423 if (DEBUG) Log.v(TAG, "onSurfaceCreated(" 424 + mSurfaceHolder + "): " + this); 425 onSurfaceCreated(mSurfaceHolder); 426 if (callbacks != null) { 427 for (SurfaceHolder.Callback c : callbacks) { 428 c.surfaceCreated(mSurfaceHolder); 429 } 430 } 431 } 432 if (forceReport || creating || formatChanged || sizeChanged) { 433 if (DEBUG) { 434 RuntimeException e = new RuntimeException(); 435 e.fillInStackTrace(); 436 Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating 437 + " formatChanged=" + formatChanged 438 + " sizeChanged=" + sizeChanged, e); 439 } 440 if (DEBUG) Log.v(TAG, "onSurfaceChanged(" 441 + mSurfaceHolder + ", " + mFormat 442 + ", " + mCurWidth + ", " + mCurHeight 443 + "): " + this); 444 onSurfaceChanged(mSurfaceHolder, mFormat, 445 mCurWidth, mCurHeight); 446 if (callbacks != null) { 447 for (SurfaceHolder.Callback c : callbacks) { 448 c.surfaceChanged(mSurfaceHolder, mFormat, 449 mCurWidth, mCurHeight); 450 } 451 } 452 } 453 } finally { 454 mIsCreating = false; 455 mCreated = true; 456 if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { 457 mSession.finishDrawing(mWindow); 458 } 459 } 460 } catch (RemoteException ex) { 461 } 462 if (DEBUG) Log.v( 463 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + 464 " w=" + mLayout.width + " h=" + mLayout.height); 465 } 466 } 467 468 void attach(IWallpaperEngineWrapper wrapper) { 469 if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper); 470 if (mDestroyed) { 471 return; 472 } 473 474 mIWallpaperEngine = wrapper; 475 mCaller = wrapper.mCaller; 476 mConnection = wrapper.mConnection; 477 mWindowToken = wrapper.mWindowToken; 478 mSurfaceHolder.setSizeFromLayout(); 479 mInitializing = true; 480 mSession = ViewRoot.getWindowSession(getMainLooper()); 481 mWindow.setSession(mSession); 482 483 if (DEBUG) Log.v(TAG, "onCreate(): " + this); 484 onCreate(mSurfaceHolder); 485 486 mInitializing = false; 487 updateSurface(false, false); 488 } 489 490 void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { 491 if (!mDestroyed) { 492 if (DEBUG) Log.v(TAG, "onDesiredSizeChanged(" 493 + desiredWidth + "," + desiredHeight + "): " + this); 494 onDesiredSizeChanged(desiredWidth, desiredHeight); 495 } 496 } 497 498 void doVisibilityChanged(boolean visible) { 499 if (!mDestroyed) { 500 mVisible = visible; 501 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible 502 + "): " + this); 503 onVisibilityChanged(visible); 504 } 505 } 506 507 void doOffsetsChanged() { 508 if (mDestroyed) { 509 return; 510 } 511 512 float xOffset; 513 float yOffset; 514 synchronized (mLock) { 515 xOffset = mPendingXOffset; 516 yOffset = mPendingYOffset; 517 mOffsetMessageEnqueued = false; 518 } 519 if (DEBUG) Log.v(TAG, "Offsets change in " + this 520 + ": " + xOffset + "," + yOffset); 521 final int availw = mIWallpaperEngine.mReqWidth-mCurWidth; 522 final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0; 523 final int availh = mIWallpaperEngine.mReqHeight-mCurHeight; 524 final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0; 525 onOffsetsChanged(xOffset, yOffset, xPixels, yPixels); 526 } 527 528 void detach() { 529 mDestroyed = true; 530 531 if (mVisible) { 532 mVisible = false; 533 if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this); 534 onVisibilityChanged(false); 535 } 536 537 if (mDestroyReportNeeded) { 538 mDestroyReportNeeded = false; 539 SurfaceHolder.Callback callbacks[]; 540 synchronized (mSurfaceHolder.mCallbacks) { 541 callbacks = new SurfaceHolder.Callback[ 542 mSurfaceHolder.mCallbacks.size()]; 543 mSurfaceHolder.mCallbacks.toArray(callbacks); 544 } 545 for (SurfaceHolder.Callback c : callbacks) { 546 c.surfaceDestroyed(mSurfaceHolder); 547 } 548 if (DEBUG) Log.v(TAG, "onSurfaceDestroyed(" 549 + mSurfaceHolder + "): " + this); 550 onSurfaceDestroyed(mSurfaceHolder); 551 } 552 553 if (DEBUG) Log.v(TAG, "onDestroy(): " + this); 554 onDestroy(); 555 556 if (mCreated) { 557 try { 558 mSession.remove(mWindow); 559 } catch (RemoteException e) { 560 } 561 mSurfaceHolder.mSurface.clear(); 562 mCreated = false; 563 } 564 } 565 } 566 567 class IWallpaperEngineWrapper extends IWallpaperEngine.Stub 568 implements HandlerCaller.Callback { 569 private final HandlerCaller mCaller; 570 571 final IWallpaperConnection mConnection; 572 final IBinder mWindowToken; 573 final int mWindowType; 574 final boolean mIsPreview; 575 int mReqWidth; 576 int mReqHeight; 577 578 Engine mEngine; 579 580 IWallpaperEngineWrapper(WallpaperService context, 581 IWallpaperConnection conn, IBinder windowToken, 582 int windowType, boolean isPreview, int reqWidth, int reqHeight) { 583 mCaller = new HandlerCaller(context, this); 584 mConnection = conn; 585 mWindowToken = windowToken; 586 mWindowType = windowType; 587 mIsPreview = isPreview; 588 mReqWidth = reqWidth; 589 mReqHeight = reqHeight; 590 591 Message msg = mCaller.obtainMessage(DO_ATTACH); 592 mCaller.sendMessage(msg); 593 } 594 595 public void setDesiredSize(int width, int height) { 596 Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height); 597 mCaller.sendMessage(msg); 598 } 599 600 public void setVisibility(boolean visible) { 601 Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED, 602 visible ? 1 : 0); 603 mCaller.sendMessage(msg); 604 } 605 606 public void destroy() { 607 Message msg = mCaller.obtainMessage(DO_DETACH); 608 mCaller.sendMessage(msg); 609 } 610 611 public void executeMessage(Message message) { 612 switch (message.what) { 613 case DO_ATTACH: { 614 try { 615 mConnection.attachEngine(this); 616 } catch (RemoteException e) { 617 Log.w(TAG, "Wallpaper host disappeared", e); 618 return; 619 } 620 Engine engine = onCreateEngine(); 621 mEngine = engine; 622 engine.attach(this); 623 return; 624 } 625 case DO_DETACH: { 626 mEngine.detach(); 627 return; 628 } 629 case DO_SET_DESIRED_SIZE: { 630 mEngine.doDesiredSizeChanged(message.arg1, message.arg2); 631 return; 632 } 633 case MSG_UPDATE_SURFACE: 634 mEngine.updateSurface(true, false); 635 break; 636 case MSG_VISIBILITY_CHANGED: 637 if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine 638 + ": " + message.arg1); 639 mEngine.doVisibilityChanged(message.arg1 != 0); 640 break; 641 case MSG_WALLPAPER_OFFSETS: { 642 mEngine.doOffsetsChanged(); 643 } break; 644 case MSG_WINDOW_RESIZED: { 645 final boolean reportDraw = message.arg1 != 0; 646 mEngine.updateSurface(true, false); 647 if (reportDraw) { 648 try { 649 mEngine.mSession.finishDrawing(mEngine.mWindow); 650 } catch (RemoteException e) { 651 } 652 } 653 } break; 654 case MSG_TOUCH_EVENT: { 655 MotionEvent ev = (MotionEvent)message.obj; 656 synchronized (mEngine.mLock) { 657 if (mEngine.mPendingMove == ev) { 658 mEngine.mPendingMove = null; 659 } 660 } 661 mEngine.onTouchEvent(ev); 662 ev.recycle(); 663 } break; 664 default : 665 Log.w(TAG, "Unknown message type " + message.what); 666 } 667 } 668 } 669 670 /** 671 * Implements the internal {@link IWallpaperService} interface to convert 672 * incoming calls to it back to calls on an {@link WallpaperService}. 673 */ 674 class IWallpaperServiceWrapper extends IWallpaperService.Stub { 675 private final WallpaperService mTarget; 676 677 public IWallpaperServiceWrapper(WallpaperService context) { 678 mTarget = context; 679 } 680 681 public void attach(IWallpaperConnection conn, IBinder windowToken, 682 int windowType, boolean isPreview, int reqWidth, int reqHeight) { 683 new IWallpaperEngineWrapper(mTarget, conn, windowToken, 684 windowType, isPreview, reqWidth, reqHeight); 685 } 686 } 687 688 /** 689 * Implement to return the implementation of the internal accessibility 690 * service interface. Subclasses should not override. 691 */ 692 @Override 693 public final IBinder onBind(Intent intent) { 694 return new IWallpaperServiceWrapper(this); 695 } 696 697 public abstract Engine onCreateEngine(); 698} 699