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 
17 package com.android.camera;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.Rect;
26 import android.hardware.Camera.Parameters;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.view.KeyEvent;
32 import android.view.View;
33 import android.view.Window;
34 import android.view.WindowManager;
35 import android.view.animation.AlphaAnimation;
36 import android.view.animation.Animation;
37 import android.view.animation.DecelerateInterpolator;
38 
39 import com.android.camera.ui.LayoutChangeNotifier;
40 import com.android.camera.ui.PopupManager;
41 import com.android.gallery3d.app.AbstractGalleryActivity;
42 import com.android.gallery3d.app.AppBridge;
43 import com.android.gallery3d.app.FilmstripPage;
44 import com.android.gallery3d.app.GalleryActionBar;
45 import com.android.gallery3d.app.PhotoPage;
46 import com.android.gallery3d.common.ApiHelper;
47 import com.android.gallery3d.ui.ScreenNail;
48 import com.android.gallery3d.util.MediaSetUtils;
49 
50 /**
51  * Superclass of camera activity.
52  */
53 public abstract class ActivityBase extends AbstractGalleryActivity
54         implements LayoutChangeNotifier.Listener {
55 
56     private static final String TAG = "ActivityBase";
57     private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
58     private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
59             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
60     public static final String ACTION_IMAGE_CAPTURE_SECURE =
61             "android.media.action.IMAGE_CAPTURE_SECURE";
62     // The intent extra for camera from secure lock screen. True if the gallery
63     // should only show newly captured pictures. sSecureAlbumId does not
64     // increment. This is used when switching between camera, camcorder, and
65     // panorama. If the extra is not set, it is in the normal camera mode.
66     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
67 
68     private int mResultCodeForTesting;
69     private Intent mResultDataForTesting;
70     private OnScreenHint mStorageHint;
71     private View mSingleTapArea;
72 
73     protected boolean mOpenCameraFail;
74     protected boolean mCameraDisabled;
75     protected CameraManager.CameraProxy mCameraDevice;
76     protected Parameters mParameters;
77     // The activity is paused. The classes that extend this class should set
78     // mPaused the first thing in onResume/onPause.
79     protected boolean mPaused;
80     protected GalleryActionBar mActionBar;
81 
82     // multiple cameras support
83     protected int mNumberOfCameras;
84     protected int mCameraId;
85     // The activity is going to switch to the specified camera id. This is
86     // needed because texture copy is done in GL thread. -1 means camera is not
87     // switching.
88     protected int mPendingSwitchCameraId = -1;
89 
90     protected MyAppBridge mAppBridge;
91     protected ScreenNail mCameraScreenNail; // This shows camera preview.
92     // The view containing only camera related widgets like control panel,
93     // indicator bar, focus indicator and etc.
94     protected View mCameraAppView;
95     protected boolean mShowCameraAppView = true;
96     private Animation mCameraAppViewFadeIn;
97     private Animation mCameraAppViewFadeOut;
98     // Secure album id. This should be incremented every time the camera is
99     // launched from the secure lock screen. The id should be the same when
100     // switching between camera, camcorder, and panorama.
101     protected static int sSecureAlbumId;
102     // True if the camera is started from secure lock screen.
103     protected boolean mSecureCamera;
104     private static boolean sFirstStartAfterScreenOn = true;
105 
106     private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
107     private static final int UPDATE_STORAGE_HINT = 0;
108     private final Handler mHandler = new Handler() {
109             @Override
110             public void handleMessage(Message msg) {
111                 switch (msg.what) {
112                     case UPDATE_STORAGE_HINT:
113                         updateStorageHint();
114                         return;
115                 }
116             }
117     };
118 
119     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
120         @Override
121         public void onReceive(Context context, Intent intent) {
122             String action = intent.getAction();
123             if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
124                     || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
125                     || action.equals(Intent.ACTION_MEDIA_CHECKING)
126                     || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
127                 updateStorageSpaceAndHint();
128             }
129         }
130     };
131 
132     // close activity when screen turns off
133     private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
134         @Override
135         public void onReceive(Context context, Intent intent) {
136             finish();
137         }
138     };
139 
140     private static BroadcastReceiver sScreenOffReceiver;
141     private static class ScreenOffReceiver extends BroadcastReceiver {
142         @Override
onReceive(Context context, Intent intent)143         public void onReceive(Context context, Intent intent) {
144             sFirstStartAfterScreenOn = true;
145         }
146     }
147 
isFirstStartAfterScreenOn()148     public static boolean isFirstStartAfterScreenOn() {
149         return sFirstStartAfterScreenOn;
150     }
151 
resetFirstStartAfterScreenOn()152     public static void resetFirstStartAfterScreenOn() {
153         sFirstStartAfterScreenOn = false;
154     }
155 
156     protected class CameraOpenThread extends Thread {
157         @Override
run()158         public void run() {
159             try {
160                 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
161                 mParameters = mCameraDevice.getParameters();
162             } catch (CameraHardwareException e) {
163                 mOpenCameraFail = true;
164             } catch (CameraDisabledException e) {
165                 mCameraDisabled = true;
166             }
167         }
168     }
169 
170     @Override
onCreate(Bundle icicle)171     public void onCreate(Bundle icicle) {
172         super.disableToggleStatusBar();
173         // Set a theme with action bar. It is not specified in manifest because
174         // we want to hide it by default. setTheme must happen before
175         // setContentView.
176         //
177         // This must be set before we call super.onCreate(), where the window's
178         // background is removed.
179         setTheme(R.style.Theme_Gallery);
180         getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
181         if (ApiHelper.HAS_ACTION_BAR) {
182             requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
183         } else {
184             requestWindowFeature(Window.FEATURE_NO_TITLE);
185         }
186 
187         // Check if this is in the secure camera mode.
188         Intent intent = getIntent();
189         String action = intent.getAction();
190         if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
191             mSecureCamera = true;
192             // Use a new album when this is started from the lock screen.
193             sSecureAlbumId++;
194         } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
195             mSecureCamera = true;
196         } else {
197             mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
198         }
199         if (mSecureCamera) {
200             IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
201             registerReceiver(mScreenOffReceiver, filter);
202             if (sScreenOffReceiver == null) {
203                 sScreenOffReceiver = new ScreenOffReceiver();
204                 getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
205             }
206         }
207         super.onCreate(icicle);
208     }
209 
isPanoramaActivity()210     public boolean isPanoramaActivity() {
211         return false;
212     }
213 
214     @Override
onResume()215     protected void onResume() {
216         super.onResume();
217 
218         installIntentFilter();
219         if (updateStorageHintOnResume()) {
220             updateStorageSpace();
221             mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200);
222         }
223     }
224 
225     @Override
onPause()226     protected void onPause() {
227         super.onPause();
228 
229         if (mStorageHint != null) {
230             mStorageHint.cancel();
231             mStorageHint = null;
232         }
233 
234         unregisterReceiver(mReceiver);
235     }
236 
237     @Override
setContentView(int layoutResID)238     public void setContentView(int layoutResID) {
239         super.setContentView(layoutResID);
240         // getActionBar() should be after setContentView
241         mActionBar = new GalleryActionBar(this);
242         mActionBar.hide();
243     }
244 
245     @Override
onSearchRequested()246     public boolean onSearchRequested() {
247         return false;
248     }
249 
250     @Override
onKeyDown(int keyCode, KeyEvent event)251     public boolean onKeyDown(int keyCode, KeyEvent event) {
252         // Prevent software keyboard or voice search from showing up.
253         if (keyCode == KeyEvent.KEYCODE_SEARCH
254                 || keyCode == KeyEvent.KEYCODE_MENU) {
255             if (event.isLongPress()) return true;
256         }
257         if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
258             return true;
259         }
260 
261         return super.onKeyDown(keyCode, event);
262     }
263 
264     @Override
onKeyUp(int keyCode, KeyEvent event)265     public boolean onKeyUp(int keyCode, KeyEvent event) {
266         if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
267             return true;
268         }
269         return super.onKeyUp(keyCode, event);
270     }
271 
setResultEx(int resultCode)272     protected void setResultEx(int resultCode) {
273         mResultCodeForTesting = resultCode;
274         setResult(resultCode);
275     }
276 
setResultEx(int resultCode, Intent data)277     protected void setResultEx(int resultCode, Intent data) {
278         mResultCodeForTesting = resultCode;
279         mResultDataForTesting = data;
280         setResult(resultCode, data);
281     }
282 
getResultCode()283     public int getResultCode() {
284         return mResultCodeForTesting;
285     }
286 
getResultData()287     public Intent getResultData() {
288         return mResultDataForTesting;
289     }
290 
291     @Override
onDestroy()292     protected void onDestroy() {
293         PopupManager.removeInstance(this);
294         if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
295         super.onDestroy();
296     }
297 
installIntentFilter()298     protected void installIntentFilter() {
299         // install an intent filter to receive SD card related events.
300         IntentFilter intentFilter =
301                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
302         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
303         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
304         intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
305         intentFilter.addDataScheme("file");
306         registerReceiver(mReceiver, intentFilter);
307     }
308 
updateStorageSpace()309     protected void updateStorageSpace() {
310         mStorageSpace = Storage.getAvailableSpace();
311     }
312 
getStorageSpace()313     protected long getStorageSpace() {
314         return mStorageSpace;
315     }
316 
updateStorageSpaceAndHint()317     protected void updateStorageSpaceAndHint() {
318         updateStorageSpace();
319         updateStorageHint(mStorageSpace);
320     }
321 
updateStorageHint()322     protected void updateStorageHint() {
323         updateStorageHint(mStorageSpace);
324     }
325 
updateStorageHintOnResume()326     protected boolean updateStorageHintOnResume() {
327         return true;
328     }
329 
updateStorageHint(long storageSpace)330     protected void updateStorageHint(long storageSpace) {
331         String message = null;
332         if (storageSpace == Storage.UNAVAILABLE) {
333             message = getString(R.string.no_storage);
334         } else if (storageSpace == Storage.PREPARING) {
335             message = getString(R.string.preparing_sd);
336         } else if (storageSpace == Storage.UNKNOWN_SIZE) {
337             message = getString(R.string.access_sd_fail);
338         } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
339             message = getString(R.string.spaceIsLow_content);
340         }
341 
342         if (message != null) {
343             if (mStorageHint == null) {
344                 mStorageHint = OnScreenHint.makeText(this, message);
345             } else {
346                 mStorageHint.setText(message);
347             }
348             mStorageHint.show();
349         } else if (mStorageHint != null) {
350             mStorageHint.cancel();
351             mStorageHint = null;
352         }
353     }
354 
gotoGallery()355     protected void gotoGallery() {
356         // Move the next picture with capture animation. "1" means next.
357         mAppBridge.switchWithCaptureAnimation(1);
358     }
359 
360     // Call this after setContentView.
createCameraScreenNail(boolean getPictures)361     public ScreenNail createCameraScreenNail(boolean getPictures) {
362         mCameraAppView = findViewById(R.id.camera_app_root);
363         Bundle data = new Bundle();
364         String path;
365         if (getPictures) {
366             if (mSecureCamera) {
367                 path = "/secure/all/" + sSecureAlbumId;
368             } else {
369                 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
370             }
371         } else {
372             path = "/local/all/0"; // Use 0 so gallery does not show anything.
373         }
374         data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
375         data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
376         data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
377 
378         // Send an AppBridge to gallery to enable the camera preview.
379         if (mAppBridge != null) {
380             mCameraScreenNail.recycle();
381         }
382         mAppBridge = new MyAppBridge();
383         data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
384         if (getStateManager().getStateCount() == 0) {
385             getStateManager().startState(FilmstripPage.class, data);
386         } else {
387             getStateManager().switchState(getStateManager().getTopState(),
388                     FilmstripPage.class, data);
389         }
390         mCameraScreenNail = mAppBridge.getCameraScreenNail();
391         return mCameraScreenNail;
392     }
393 
394     // Call this after setContentView.
reuseCameraScreenNail(boolean getPictures)395     protected ScreenNail reuseCameraScreenNail(boolean getPictures) {
396         mCameraAppView = findViewById(R.id.camera_app_root);
397         Bundle data = new Bundle();
398         String path;
399         if (getPictures) {
400             if (mSecureCamera) {
401                 path = "/secure/all/" + sSecureAlbumId;
402             } else {
403                 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
404             }
405         } else {
406             path = "/local/all/0"; // Use 0 so gallery does not show anything.
407         }
408         data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
409         data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
410         data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
411 
412         // Send an AppBridge to gallery to enable the camera preview.
413         if (mAppBridge == null) {
414             mAppBridge = new MyAppBridge();
415         }
416         data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
417         if (getStateManager().getStateCount() == 0) {
418             getStateManager().startState(FilmstripPage.class, data);
419         }
420         mCameraScreenNail = mAppBridge.getCameraScreenNail();
421         return mCameraScreenNail;
422     }
423 
424     private class HideCameraAppView implements Animation.AnimationListener {
425         @Override
onAnimationEnd(Animation animation)426         public void onAnimationEnd(Animation animation) {
427             // We cannot set this as GONE because we want to receive the
428             // onLayoutChange() callback even when we are invisible.
429             mCameraAppView.setVisibility(View.INVISIBLE);
430         }
431 
432         @Override
onAnimationRepeat(Animation animation)433         public void onAnimationRepeat(Animation animation) {
434         }
435 
436         @Override
onAnimationStart(Animation animation)437         public void onAnimationStart(Animation animation) {
438         }
439     }
440 
updateCameraAppView()441     protected void updateCameraAppView() {
442         // Initialize the animation.
443         if (mCameraAppViewFadeIn == null) {
444             mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f);
445             mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
446             mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator());
447 
448             mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f);
449             mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
450             mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator());
451             mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView());
452         }
453 
454         if (mShowCameraAppView) {
455             mCameraAppView.setVisibility(View.VISIBLE);
456             // The "transparent region" is not recomputed when a sibling of
457             // SurfaceView changes visibility (unless it involves GONE). It's
458             // been broken since 1.0. Call requestLayout to work around it.
459             mCameraAppView.requestLayout();
460             mCameraAppView.startAnimation(mCameraAppViewFadeIn);
461         } else {
462             mCameraAppView.startAnimation(mCameraAppViewFadeOut);
463         }
464     }
465 
onFullScreenChanged(boolean full)466     protected void onFullScreenChanged(boolean full) {
467         if (mShowCameraAppView == full) return;
468         mShowCameraAppView = full;
469         if (mPaused || isFinishing()) return;
470         updateCameraAppView();
471     }
472 
473     @Override
getGalleryActionBar()474     public GalleryActionBar getGalleryActionBar() {
475         return mActionBar;
476     }
477 
478     // Preview frame layout has changed.
479     @Override
onLayoutChange(View v, int left, int top, int right, int bottom)480     public void onLayoutChange(View v, int left, int top, int right, int bottom) {
481         if (mAppBridge == null) return;
482 
483         int width = right - left;
484         int height = bottom - top;
485         if (ApiHelper.HAS_SURFACE_TEXTURE) {
486             CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail;
487             if (Util.getDisplayRotation(this) % 180 == 0) {
488                 screenNail.setPreviewFrameLayoutSize(width, height);
489             } else {
490                 // Swap the width and height. Camera screen nail draw() is based on
491                 // natural orientation, not the view system orientation.
492                 screenNail.setPreviewFrameLayoutSize(height, width);
493             }
494             notifyScreenNailChanged();
495         }
496     }
497 
setSingleTapUpListener(View singleTapArea)498     protected void setSingleTapUpListener(View singleTapArea) {
499         mSingleTapArea = singleTapArea;
500     }
501 
onSingleTapUp(int x, int y)502     private boolean onSingleTapUp(int x, int y) {
503         // Ignore if listener is null or the camera control is invisible.
504         if (mSingleTapArea == null || !mShowCameraAppView) return false;
505 
506         int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
507                 mSingleTapArea);
508         x -= relativeLocation[0];
509         y -= relativeLocation[1];
510         if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
511                 && y < mSingleTapArea.getHeight()) {
512             onSingleTapUp(mSingleTapArea, x, y);
513             return true;
514         }
515         return false;
516     }
517 
onSingleTapUp(View view, int x, int y)518     protected void onSingleTapUp(View view, int x, int y) {
519     }
520 
setSwipingEnabled(boolean enabled)521     public void setSwipingEnabled(boolean enabled) {
522         mAppBridge.setSwipingEnabled(enabled);
523     }
524 
notifyScreenNailChanged()525     public void notifyScreenNailChanged() {
526         mAppBridge.notifyScreenNailChanged();
527     }
528 
onPreviewTextureCopied()529     protected void onPreviewTextureCopied() {
530     }
531 
onCaptureTextureCopied()532     protected void onCaptureTextureCopied() {
533     }
534 
addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri)535     protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) {
536         if (mSecureCamera) {
537             int id = Integer.parseInt(uri.getLastPathSegment());
538             mAppBridge.addSecureAlbumItem(isVideo, id);
539         }
540     }
541 
isSecureCamera()542     public boolean isSecureCamera() {
543         return mSecureCamera;
544     }
545 
546     //////////////////////////////////////////////////////////////////////////
547     //  The is the communication interface between the Camera Application and
548     //  the Gallery PhotoPage.
549     //////////////////////////////////////////////////////////////////////////
550 
551     class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
552         @SuppressWarnings("hiding")
553         private ScreenNail mCameraScreenNail;
554         private Server mServer;
555 
556         @Override
attachScreenNail()557         public ScreenNail attachScreenNail() {
558             if (mCameraScreenNail == null) {
559                 if (ApiHelper.HAS_SURFACE_TEXTURE) {
560                     mCameraScreenNail = new CameraScreenNail(this);
561                 } else {
562                     Bitmap b = BitmapFactory.decodeResource(getResources(),
563                             R.drawable.wallpaper_picker_preview);
564                     mCameraScreenNail = new StaticBitmapScreenNail(b);
565                 }
566             }
567             return mCameraScreenNail;
568         }
569 
570         @Override
detachScreenNail()571         public void detachScreenNail() {
572             mCameraScreenNail = null;
573         }
574 
getCameraScreenNail()575         public ScreenNail getCameraScreenNail() {
576             return mCameraScreenNail;
577         }
578 
579         // Return true if the tap is consumed.
580         @Override
onSingleTapUp(int x, int y)581         public boolean onSingleTapUp(int x, int y) {
582             return ActivityBase.this.onSingleTapUp(x, y);
583         }
584 
585         // This is used to notify that the screen nail will be drawn in full screen
586         // or not in next draw() call.
587         @Override
onFullScreenChanged(boolean full)588         public void onFullScreenChanged(boolean full) {
589             ActivityBase.this.onFullScreenChanged(full);
590         }
591 
592         @Override
requestRender()593         public void requestRender() {
594             getGLRoot().requestRenderForced();
595         }
596 
597         @Override
onPreviewTextureCopied()598         public void onPreviewTextureCopied() {
599             ActivityBase.this.onPreviewTextureCopied();
600         }
601 
602         @Override
onCaptureTextureCopied()603         public void onCaptureTextureCopied() {
604             ActivityBase.this.onCaptureTextureCopied();
605         }
606 
607         @Override
setServer(Server s)608         public void setServer(Server s) {
609             mServer = s;
610         }
611 
612         @Override
isPanorama()613         public boolean isPanorama() {
614             return ActivityBase.this.isPanoramaActivity();
615         }
616 
617         @Override
isStaticCamera()618         public boolean isStaticCamera() {
619             return !ApiHelper.HAS_SURFACE_TEXTURE;
620         }
621 
addSecureAlbumItem(boolean isVideo, int id)622         public void addSecureAlbumItem(boolean isVideo, int id) {
623             if (mServer != null) mServer.addSecureAlbumItem(isVideo, id);
624         }
625 
setCameraRelativeFrame(Rect frame)626         private void setCameraRelativeFrame(Rect frame) {
627             if (mServer != null) mServer.setCameraRelativeFrame(frame);
628         }
629 
switchWithCaptureAnimation(int offset)630         private void switchWithCaptureAnimation(int offset) {
631             if (mServer != null) mServer.switchWithCaptureAnimation(offset);
632         }
633 
setSwipingEnabled(boolean enabled)634         private void setSwipingEnabled(boolean enabled) {
635             if (mServer != null) mServer.setSwipingEnabled(enabled);
636         }
637 
notifyScreenNailChanged()638         private void notifyScreenNailChanged() {
639             if (mServer != null) mServer.notifyScreenNailChanged();
640         }
641     }
642 }
643