• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.internal.policy.impl;
18 
19 import com.android.internal.R;
20 import com.android.internal.telephony.IccCard;
21 import com.android.internal.widget.LockPatternUtils;
22 
23 import android.accounts.Account;
24 import android.accounts.AccountManager;
25 import android.accounts.AccountManagerCallback;
26 import android.accounts.AccountManagerFuture;
27 import android.accounts.AuthenticatorException;
28 import android.accounts.OperationCanceledException;
29 import android.app.AlertDialog;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.graphics.Bitmap;
33 import android.graphics.Canvas;
34 import android.graphics.ColorFilter;
35 import android.graphics.PixelFormat;
36 import android.graphics.drawable.BitmapDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.os.SystemProperties;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.view.KeyEvent;
42 import android.view.View;
43 import android.view.WindowManager;
44 
45 import java.io.IOException;
46 
47 /**
48  * The host view for all of the screens of the pattern unlock screen.  There are
49  * two {@link Mode}s of operation, lock and unlock.  This will show the appropriate
50  * screen, and listen for callbacks via
51  * {@link com.android.internal.policy.impl.KeyguardScreenCallback}
52  * from the current screen.
53  *
54  * This view, in turn, communicates back to
55  * {@link com.android.internal.policy.impl.KeyguardViewManager}
56  * via its {@link com.android.internal.policy.impl.KeyguardViewCallback}, as appropriate.
57  */
58 public class LockPatternKeyguardView extends KeyguardViewBase
59         implements AccountManagerCallback<Account[]> {
60 
61     // intent action for launching emergency dialer activity.
62     static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
63 
64     private static final boolean DEBUG = false;
65     private static final String TAG = "LockPatternKeyguardView";
66 
67     private final KeyguardUpdateMonitor mUpdateMonitor;
68     private final KeyguardWindowController mWindowController;
69 
70     private View mLockScreen;
71     private View mUnlockScreen;
72 
73     private boolean mScreenOn = false;
74     private boolean mEnableFallback = false; // assume no fallback UI until we know better
75 
76 
77     /**
78      * The current {@link KeyguardScreen} will use this to communicate back to us.
79      */
80     KeyguardScreenCallback mKeyguardScreenCallback;
81 
82 
83     private boolean mRequiresSim;
84 
85 
86     /**
87      * Either a lock screen (an informational keyguard screen), or an unlock
88      * screen (a means for unlocking the device) is shown at any given time.
89      */
90     enum Mode {
91         LockScreen,
92         UnlockScreen
93     }
94 
95     /**
96      * The different types screens available for {@link Mode#UnlockScreen}.
97      * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode()
98      */
99     enum UnlockMode {
100 
101         /**
102          * Unlock by drawing a pattern.
103          */
104         Pattern,
105 
106         /**
107          * Unlock by entering a sim pin.
108          */
109         SimPin,
110 
111         /**
112          * Unlock by entering an account's login and password.
113          */
114         Account
115     }
116 
117     /**
118      * The current mode.
119      */
120     private Mode mMode = Mode.LockScreen;
121 
122     /**
123      * Keeps track of what mode the current unlock screen is (cached from most recent computation in
124      * {@link #getUnlockMode}).
125      */
126     private UnlockMode mUnlockScreenMode;
127 
128     private boolean mForgotPattern;
129 
130     /**
131      * If true, it means we are in the process of verifying that the user
132      * can get past the lock screen per {@link #verifyUnlock()}
133      */
134     private boolean mIsVerifyUnlockOnly = false;
135 
136 
137     /**
138      * Used to lookup the state of the lock pattern
139      */
140     private final LockPatternUtils mLockPatternUtils;
141 
142     /**
143      * @return Whether we are stuck on the lock screen because the sim is
144      *   missing.
145      */
stuckOnLockScreenBecauseSimMissing()146     private boolean stuckOnLockScreenBecauseSimMissing() {
147         return mRequiresSim
148                 && (!mUpdateMonitor.isDeviceProvisioned())
149                 && (mUpdateMonitor.getSimState() == IccCard.State.ABSENT);
150     }
151 
run(AccountManagerFuture<Account[]> future)152     public void run(AccountManagerFuture<Account[]> future) {
153         // We err on the side of caution.
154         // In case of error we assume we have a SAML account.
155         boolean hasSAMLAccount = true;
156         try {
157             hasSAMLAccount = future.getResult().length > 0;
158         } catch (OperationCanceledException e) {
159         } catch (IOException e) {
160         } catch (AuthenticatorException e) {
161         }
162         mEnableFallback = !hasSAMLAccount;
163 
164         if (mUnlockScreen == null) {
165             Log.w(TAG, "no unlock screen when receiving AccountManager information");
166         } else if (mUnlockScreen instanceof UnlockScreen) {
167             ((UnlockScreen)mUnlockScreen).setEnableFallback(true);
168         }
169     }
170 
171     /**
172      * @param context Used to inflate, and create views.
173      * @param updateMonitor Knows the state of the world, and passed along to each
174      *   screen so they can use the knowledge, and also register for callbacks
175      *   on dynamic information.
176      * @param lockPatternUtils Used to look up state of lock pattern.
177      */
LockPatternKeyguardView( Context context, KeyguardUpdateMonitor updateMonitor, LockPatternUtils lockPatternUtils, KeyguardWindowController controller)178     public LockPatternKeyguardView(
179             Context context,
180             KeyguardUpdateMonitor updateMonitor,
181             LockPatternUtils lockPatternUtils,
182             KeyguardWindowController controller) {
183         super(context);
184 
185         mEnableFallback = false;
186 
187         mRequiresSim =
188                 TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim"));
189 
190         mUpdateMonitor = updateMonitor;
191         mLockPatternUtils = lockPatternUtils;
192         mWindowController = controller;
193 
194         mMode = getInitialMode();
195 
196         mKeyguardScreenCallback = new KeyguardScreenCallback() {
197 
198             public void goToLockScreen() {
199                 mForgotPattern = false;
200                 if (mIsVerifyUnlockOnly) {
201                     // navigating away from unlock screen during verify mode means
202                     // we are done and the user failed to authenticate.
203                     mIsVerifyUnlockOnly = false;
204                     getCallback().keyguardDone(false);
205                 } else {
206                     updateScreen(Mode.LockScreen);
207                 }
208             }
209 
210             public void goToUnlockScreen() {
211                 final IccCard.State simState = mUpdateMonitor.getSimState();
212                 if (stuckOnLockScreenBecauseSimMissing()
213                          || (simState == IccCard.State.PUK_REQUIRED)){
214                     // stuck on lock screen when sim missing or puk'd
215                     return;
216                 }
217                 if (!isSecure()) {
218                     getCallback().keyguardDone(true);
219                 } else {
220                     updateScreen(Mode.UnlockScreen);
221                 }
222             }
223 
224             public void forgotPattern(boolean isForgotten) {
225                 if (mEnableFallback) {
226                     mForgotPattern = isForgotten;
227                     updateScreen(Mode.UnlockScreen);
228                 }
229             }
230 
231             public boolean isSecure() {
232                 return LockPatternKeyguardView.this.isSecure();
233             }
234 
235             public boolean isVerifyUnlockOnly() {
236                 return mIsVerifyUnlockOnly;
237             }
238 
239             public void recreateMe() {
240                 recreateScreens();
241             }
242 
243             public void takeEmergencyCallAction() {
244                 Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
245                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
246                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
247                 getContext().startActivity(intent);
248             }
249 
250             public void pokeWakelock() {
251                 getCallback().pokeWakelock();
252             }
253 
254             public void pokeWakelock(int millis) {
255                 getCallback().pokeWakelock(millis);
256             }
257 
258             public void keyguardDone(boolean authenticated) {
259                 getCallback().keyguardDone(authenticated);
260             }
261 
262             public void keyguardDoneDrawing() {
263                 // irrelevant to keyguard screen, they shouldn't be calling this
264             }
265 
266             public void reportFailedPatternAttempt() {
267                 mUpdateMonitor.reportFailedAttempt();
268                 final int failedAttempts = mUpdateMonitor.getFailedAttempts();
269                 if (DEBUG) Log.d(TAG,
270                     "reportFailedPatternAttempt: #" + failedAttempts +
271                     " (enableFallback=" + mEnableFallback + ")");
272                 if (mEnableFallback && failedAttempts ==
273                         (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
274                                 - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
275                     showAlmostAtAccountLoginDialog();
276                 } else if (mEnableFallback
277                         && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
278                     mLockPatternUtils.setPermanentlyLocked(true);
279                     updateScreen(mMode);
280                 } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)
281                         == 0) {
282                     showTimeoutDialog();
283                 }
284             }
285 
286             public boolean doesFallbackUnlockScreenExist() {
287                 return mEnableFallback;
288             }
289         };
290 
291         /**
292          * We'll get key events the current screen doesn't use. see
293          * {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)}
294          */
295         setFocusableInTouchMode(true);
296         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
297 
298         // wall paper background
299         if (false) {
300             final BitmapDrawable drawable = (BitmapDrawable) context.getWallpaper();
301             setBackgroundDrawable(
302                     new FastBitmapDrawable(drawable.getBitmap()));
303         }
304 
305         // create both the lock and unlock screen so they are quickly available
306         // when the screen turns on
307         mLockScreen = createLockScreen();
308         addView(mLockScreen);
309         final UnlockMode unlockMode = getUnlockMode();
310         if (DEBUG) Log.d(TAG,
311             "LockPatternKeyguardView ctor: about to createUnlockScreenFor; mEnableFallback="
312             + mEnableFallback);
313         mUnlockScreen = createUnlockScreenFor(unlockMode);
314         mUnlockScreenMode = unlockMode;
315 
316         // Ask the account manager if we have an account that can be used as a
317         // fallback in case the user forgets his pattern. The response comes
318         // back in run() below; don't bother asking until you've called
319         // createUnlockScreenFor(), else the information will go unused.
320         final boolean hasAccount = AccountManager.get(context).getAccounts().length > 0;
321         if (hasAccount) {
322             /* If we have a SAML account which requires web login we can not use the
323              fallback screen UI to ask the user for credentials.
324              For now we will disable fallback screen in this case.
325              Ultimately we could consider bringing up a web login from GLS
326              but need to make sure that it will work in the "locked screen" mode. */
327             String[] features = new String[] {"saml"};
328             AccountManager.get(context).getAccountsByTypeAndFeatures(
329                     "com.google", features, this, null);
330         }
331 
332         addView(mUnlockScreen);
333         updateScreen(mMode);
334     }
335 
336 
337     @Override
reset()338     public void reset() {
339         mIsVerifyUnlockOnly = false;
340         mForgotPattern = false;
341         updateScreen(getInitialMode());
342     }
343 
344     @Override
onScreenTurnedOff()345     public void onScreenTurnedOff() {
346         mScreenOn = false;
347         mForgotPattern = false;
348         if (mMode == Mode.LockScreen) {
349            ((KeyguardScreen) mLockScreen).onPause();
350         } else {
351             ((KeyguardScreen) mUnlockScreen).onPause();
352         }
353     }
354 
355     @Override
onScreenTurnedOn()356     public void onScreenTurnedOn() {
357         mScreenOn = true;
358         if (mMode == Mode.LockScreen) {
359            ((KeyguardScreen) mLockScreen).onResume();
360         } else {
361             ((KeyguardScreen) mUnlockScreen).onResume();
362         }
363     }
364 
365 
recreateScreens()366     private void recreateScreens() {
367         if (mLockScreen.getVisibility() == View.VISIBLE) {
368             ((KeyguardScreen) mLockScreen).onPause();
369         }
370         ((KeyguardScreen) mLockScreen).cleanUp();
371         removeViewInLayout(mLockScreen);
372 
373         mLockScreen = createLockScreen();
374         mLockScreen.setVisibility(View.INVISIBLE);
375         addView(mLockScreen);
376 
377         if (mUnlockScreen.getVisibility() == View.VISIBLE) {
378             ((KeyguardScreen) mUnlockScreen).onPause();
379         }
380         ((KeyguardScreen) mUnlockScreen).cleanUp();
381         removeViewInLayout(mUnlockScreen);
382 
383         final UnlockMode unlockMode = getUnlockMode();
384         mUnlockScreen = createUnlockScreenFor(unlockMode);
385         mUnlockScreen.setVisibility(View.INVISIBLE);
386         mUnlockScreenMode = unlockMode;
387         addView(mUnlockScreen);
388 
389         updateScreen(mMode);
390     }
391 
392 
393     @Override
wakeWhenReadyTq(int keyCode)394     public void wakeWhenReadyTq(int keyCode) {
395         if (DEBUG) Log.d(TAG, "onWakeKey");
396         if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen)
397                 && (mUpdateMonitor.getSimState() != IccCard.State.PUK_REQUIRED)) {
398             if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
399             updateScreen(Mode.UnlockScreen);
400             getCallback().pokeWakelock();
401         } else {
402             if (DEBUG) Log.d(TAG, "poking wake lock immediately");
403             getCallback().pokeWakelock();
404         }
405     }
406 
407     @Override
verifyUnlock()408     public void verifyUnlock() {
409         if (!isSecure()) {
410             // non-secure keyguard screens are successfull by default
411             getCallback().keyguardDone(true);
412         } else if (mUnlockScreenMode != UnlockMode.Pattern) {
413             // can only verify unlock when in pattern mode
414             getCallback().keyguardDone(false);
415         } else {
416             // otherwise, go to the unlock screen, see if they can verify it
417             mIsVerifyUnlockOnly = true;
418             updateScreen(Mode.UnlockScreen);
419         }
420     }
421 
422     @Override
cleanUp()423     public void cleanUp() {
424         ((KeyguardScreen) mLockScreen).onPause();
425         ((KeyguardScreen) mLockScreen).cleanUp();
426         ((KeyguardScreen) mUnlockScreen).onPause();
427         ((KeyguardScreen) mUnlockScreen).cleanUp();
428     }
429 
isSecure()430     private boolean isSecure() {
431         UnlockMode unlockMode = getUnlockMode();
432         if (unlockMode == UnlockMode.Pattern) {
433             return mLockPatternUtils.isLockPatternEnabled();
434         } else if (unlockMode == UnlockMode.SimPin) {
435             return mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED
436                         || mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED;
437         } else if (unlockMode == UnlockMode.Account) {
438             return true;
439         } else {
440             throw new IllegalStateException("unknown unlock mode " + unlockMode);
441         }
442     }
443 
updateScreen(final Mode mode)444     private void updateScreen(final Mode mode) {
445 
446         mMode = mode;
447 
448         final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen;
449         final View visibleScreen = (mode == Mode.LockScreen)
450                 ? mLockScreen : getUnlockScreenForCurrentUnlockMode();
451 
452         // do this before changing visibility so focus isn't requested before the input
453         // flag is set
454         mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput());
455 
456 
457         if (mScreenOn) {
458             if (goneScreen.getVisibility() == View.VISIBLE) {
459                 ((KeyguardScreen) goneScreen).onPause();
460             }
461             if (visibleScreen.getVisibility() != View.VISIBLE) {
462                 ((KeyguardScreen) visibleScreen).onResume();
463             }
464         }
465 
466         goneScreen.setVisibility(View.GONE);
467         visibleScreen.setVisibility(View.VISIBLE);
468 
469 
470         if (!visibleScreen.requestFocus()) {
471             throw new IllegalStateException("keyguard screen must be able to take "
472                     + "focus when shown " + visibleScreen.getClass().getCanonicalName());
473         }
474     }
475 
createLockScreen()476     View createLockScreen() {
477         return new LockScreen(
478                 mContext,
479                 mLockPatternUtils,
480                 mUpdateMonitor,
481                 mKeyguardScreenCallback);
482     }
483 
createUnlockScreenFor(UnlockMode unlockMode)484     View createUnlockScreenFor(UnlockMode unlockMode) {
485         if (unlockMode == UnlockMode.Pattern) {
486             UnlockScreen view = new UnlockScreen(
487                     mContext,
488                     mLockPatternUtils,
489                     mUpdateMonitor,
490                     mKeyguardScreenCallback,
491                     mUpdateMonitor.getFailedAttempts());
492             if (DEBUG) Log.d(TAG,
493                 "createUnlockScreenFor(" + unlockMode + "): mEnableFallback=" + mEnableFallback);
494             view.setEnableFallback(mEnableFallback);
495             return view;
496         } else if (unlockMode == UnlockMode.SimPin) {
497             return new SimUnlockScreen(
498                     mContext,
499                     mUpdateMonitor,
500                     mKeyguardScreenCallback);
501         } else if (unlockMode == UnlockMode.Account) {
502             try {
503                 return new AccountUnlockScreen(
504                         mContext,
505                         mKeyguardScreenCallback,
506                         mLockPatternUtils);
507             } catch (IllegalStateException e) {
508                 Log.i(TAG, "Couldn't instantiate AccountUnlockScreen"
509                       + " (IAccountsService isn't available)");
510                 // TODO: Need a more general way to provide a
511                 // platform-specific fallback UI here.
512                 // For now, if we can't display the account login
513                 // unlock UI, just bring back the regular "Pattern" unlock mode.
514 
515                 // (We do this by simply returning a regular UnlockScreen
516                 // here.  This means that the user will still see the
517                 // regular pattern unlock UI, regardless of the value of
518                 // mUnlockScreenMode or whether or not we're in the
519                 // "permanently locked" state.)
520                 return createUnlockScreenFor(UnlockMode.Pattern);
521             }
522         } else {
523             throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
524         }
525     }
526 
getUnlockScreenForCurrentUnlockMode()527     private View getUnlockScreenForCurrentUnlockMode() {
528         final UnlockMode unlockMode = getUnlockMode();
529 
530         // if a screen exists for the correct mode, we're done
531         if (unlockMode == mUnlockScreenMode) {
532             return mUnlockScreen;
533         }
534 
535         // remember the mode
536         mUnlockScreenMode = unlockMode;
537 
538         // unlock mode has changed and we have an existing old unlock screen
539         // to clean up
540         if (mScreenOn && (mUnlockScreen.getVisibility() == View.VISIBLE)) {
541             ((KeyguardScreen) mUnlockScreen).onPause();
542         }
543         ((KeyguardScreen) mUnlockScreen).cleanUp();
544         removeViewInLayout(mUnlockScreen);
545 
546         // create the new one
547         mUnlockScreen = createUnlockScreenFor(unlockMode);
548         mUnlockScreen.setVisibility(View.INVISIBLE);
549         addView(mUnlockScreen);
550         return mUnlockScreen;
551     }
552 
553     /**
554      * Given the current state of things, what should be the initial mode of
555      * the lock screen (lock or unlock).
556      */
getInitialMode()557     private Mode getInitialMode() {
558         final IccCard.State simState = mUpdateMonitor.getSimState();
559         if (stuckOnLockScreenBecauseSimMissing() || (simState == IccCard.State.PUK_REQUIRED)) {
560             return Mode.LockScreen;
561         } else if (isSecure()) {
562             return Mode.UnlockScreen;
563         } else {
564             return Mode.LockScreen;
565         }
566     }
567 
568     /**
569      * Given the current state of things, what should the unlock screen be?
570      */
getUnlockMode()571     private UnlockMode getUnlockMode() {
572         final IccCard.State simState = mUpdateMonitor.getSimState();
573         if (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED) {
574             return UnlockMode.SimPin;
575         } else {
576             return (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) ?
577                     UnlockMode.Account:
578                     UnlockMode.Pattern;
579         }
580     }
581 
showTimeoutDialog()582     private void showTimeoutDialog() {
583         int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
584         String message = mContext.getString(
585                 R.string.lockscreen_too_many_failed_attempts_dialog_message,
586                 mUpdateMonitor.getFailedAttempts(),
587                 timeoutInSeconds);
588         final AlertDialog dialog = new AlertDialog.Builder(mContext)
589                 .setTitle(null)
590                 .setMessage(message)
591                 .setNeutralButton(R.string.ok, null)
592                 .create();
593         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
594         if (!mContext.getResources().getBoolean(
595                 com.android.internal.R.bool.config_sf_slowBlur)) {
596             dialog.getWindow().setFlags(
597                     WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
598                     WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
599         }
600         dialog.show();
601     }
602 
showAlmostAtAccountLoginDialog()603     private void showAlmostAtAccountLoginDialog() {
604         int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
605         String message = mContext.getString(
606                 R.string.lockscreen_failed_attempts_almost_glogin,
607                 LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
608                 - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
609                 LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
610                 timeoutInSeconds);
611         final AlertDialog dialog = new AlertDialog.Builder(mContext)
612                 .setTitle(null)
613                 .setMessage(message)
614                 .setNeutralButton(R.string.ok, null)
615                 .create();
616         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
617         if (!mContext.getResources().getBoolean(
618                 com.android.internal.R.bool.config_sf_slowBlur)) {
619             dialog.getWindow().setFlags(
620                     WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
621                     WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
622         }
623         dialog.show();
624     }
625 
626     /**
627      * Used to put wallpaper on the background of the lock screen.  Centers it
628      * Horizontally and pins the bottom (assuming that the lock screen is aligned
629      * with the bottom, so the wallpaper should extend above the top into the
630      * status bar).
631      */
632     static private class FastBitmapDrawable extends Drawable {
633         private Bitmap mBitmap;
634         private int mOpacity;
635 
FastBitmapDrawable(Bitmap bitmap)636         private FastBitmapDrawable(Bitmap bitmap) {
637             mBitmap = bitmap;
638             mOpacity = mBitmap.hasAlpha() ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
639         }
640 
641         @Override
draw(Canvas canvas)642         public void draw(Canvas canvas) {
643             canvas.drawBitmap(
644                     mBitmap,
645                     (getBounds().width() - mBitmap.getWidth()) / 2,
646                     (getBounds().height() - mBitmap.getHeight()),
647                     null);
648         }
649 
650         @Override
getOpacity()651         public int getOpacity() {
652             return mOpacity;
653         }
654 
655         @Override
setAlpha(int alpha)656         public void setAlpha(int alpha) {
657         }
658 
659         @Override
setColorFilter(ColorFilter cf)660         public void setColorFilter(ColorFilter cf) {
661         }
662 
663         @Override
getIntrinsicWidth()664         public int getIntrinsicWidth() {
665             return mBitmap.getWidth();
666         }
667 
668         @Override
getIntrinsicHeight()669         public int getIntrinsicHeight() {
670             return mBitmap.getHeight();
671         }
672 
673         @Override
getMinimumWidth()674         public int getMinimumWidth() {
675             return mBitmap.getWidth();
676         }
677 
678         @Override
getMinimumHeight()679         public int getMinimumHeight() {
680             return mBitmap.getHeight();
681         }
682     }
683 }
684 
685