KeyguardViewMediatorpublic class KeyguardViewMediator extends Object implements KeyguardUpdateMonitor.SimStateCallback, KeyguardUpdateMonitor.ConfigurationChangeCallback, KeyguardViewCallbackMediates requests related to the keyguard. This includes queries about the
state of the keyguard, power management events that effect whether the keyguard
should be shown or reset, callbacks to the phone window manager to notify
it of when the keyguard is showing, and events from the keyguard view itself
stating that the keyguard was succesfully unlocked.
Note that the keyguard view is shown when the screen is off (as appropriate)
so that once the screen comes on, it will be ready immediately.
Example queries about the keyguard:
- is {movement, key} one that should wake the keygaurd?
- is the keyguard showing?
- are input events restricted due to the state of the keyguard?
Callbacks to the phone window manager:
- the keyguard is showing
Example external events that translate to keyguard view changes:
- screen turned off -> reset the keyguard, and show it so it will be ready
next time the screen turns on
- keyboard is slid open -> if the keyguard is not secure, hide it
Events from the keyguard view:
- user succesfully unlocked keyguard -> hide keyguard view, and no longer
restrict input events.
Note: in addition to normal power managment events that effect the state of
whether the keyguard should be showing, external apps and services may request
that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
false, this will override all other conditions for turning on the keyguard.
Threading and synchronization:
This class is created by the initialization routine of the {@link WindowManagerPolicy},
and runs on its thread. The keyguard UI is created from that thread in the
constructor of this class. The apis may be called from other threads, including the
{@link com.android.server.KeyInputQueue}'s and {@link android.view.WindowManager}'s.
Therefore, methods on this class are synchronized, and any action that is pointed
directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI
thread of the keyguard. |
Fields Summary |
---|
private static final boolean | DEBUG | private static final boolean | DBG_WAKE | private static final String | TAG | private static final String | DELAYED_KEYGUARD_ACTION | private static final int | TIMEOUT | private static final int | SHOW | private static final int | HIDE | private static final int | RESET | private static final int | VERIFY_UNLOCK | private static final int | NOTIFY_SCREEN_OFF | private static final int | NOTIFY_SCREEN_ON | private static final int | WAKE_WHEN_READY | private static final int | KEYGUARD_DONE | private static final int | KEYGUARD_DONE_DRAWING | protected static final int | AWAKE_INTERVAL_DEFAULT_MSThe default amount of time we stay awake (used for all key input) | protected static final int | AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_MSThe default amount of time we stay awake (used for all key input) when
the keyboard is open | private static final int | KEYGUARD_DELAY_MSHow long to wait after the screen turns off due to timeout before
turning on the keyguard (i.e, the user has this much time to turn
the screen back on without having to face the keyguard). | private static final int | KEYGUARD_DONE_DRAWING_TIMEOUT_MSHow long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()}
callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
that is reenabling the keyguard. | private android.content.Context | mContext | private android.app.AlarmManager | mAlarmManager | private boolean | mSystemReady | android.os.LocalPowerManager | mRealPowerManagerLow level access to the power manager for enableUserActivity. Having this
requires that we run in the system process. | private android.os.PowerManager | mPMHigh level access to the power manager for WakeLocks | private PowerManager.WakeLock | mWakeLockUsed to keep the device awake while the keyguard is showing, i.e for
calls to {@link #pokeWakelock()} | private PowerManager.WakeLock | mWakeAndHandOffDoes not turn on screen, held while a call to {@link KeyguardViewManager#wakeWhenReadyTq(int)}
is called to make sure the device doesn't sleep before it has a chance to poke
the wake lock. | private android.app.StatusBarManager | mStatusBarManagerUsed to disable / reenable status bar expansion. | private KeyguardViewManager | mKeyguardViewManager | private boolean | mExternallyEnabledExternal apps (like the phone app) can tell us to disable the keygaurd. | private boolean | mNeedToReshowWhenReenabledRemember if an external call to {@link #setKeyguardEnabled} with value
false caused us to hide the keyguard, so that we need to reshow it once
the keygaurd is reenabled with another call with value true. | private boolean | mShowing | private int | mDelayedShowingSequenceHelps remember whether the screen has turned on since the last time
it turned off due to timeout. see {@link #onScreenTurnedOff(int)} | private int | mWakelockSequence | private PhoneWindowManager | mCallback | private WindowManagerPolicy.OnKeyguardExitResult | mExitSecureCallbackIf the user has disabled the keyguard, then requests to exit, this is
how we'll ultimately let them know whether it was successful. We use this
var being non-null as an indicator that there is an in progress request. | private KeyguardViewProperties | mKeyguardViewProperties | private KeyguardUpdateMonitor | mUpdateMonitor | private boolean | mKeyboardOpen | private boolean | mScreenOn | private android.content.Intent | mUserPresentIntentwe send this intent when the keyguard is dismissed. | private boolean | mWaitingUntilKeyguardVisible{@link #setKeyguardEnabled} waits on this condition when it reenables
the keyguard. | private android.content.BroadcastReceiver | mBroadCastReceiver | private android.os.Handler | mHandlerThis handler will be associated with the policy thread, which will also
be the UI thread of the keyguard. Since the apis of the policy, and therefore
this class, can be called by other threads, any action that directly
interacts with the keyguard ui should be posted to this handler, rather
than called directly. |
Constructors Summary |
---|
public KeyguardViewMediator(android.content.Context context, PhoneWindowManager callback, android.os.LocalPowerManager powerManager)
mContext = context;
mRealPowerManager = powerManager;
mPM = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPM.newWakeLock(
PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
"keyguard");
mWakeLock.setReferenceCounted(false);
mWakeAndHandOff = mPM.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"keyguardWakeAndHandOff");
mWakeAndHandOff.setReferenceCounted(false);
IntentFilter filter = new IntentFilter();
filter.addAction(DELAYED_KEYGUARD_ACTION);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
context.registerReceiver(mBroadCastReceiver, filter);
mAlarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
mCallback = callback;
mUpdateMonitor = new KeyguardUpdateMonitor(context);
mUpdateMonitor.registerConfigurationChangeCallback(this);
mUpdateMonitor.registerSimStateCallback(this);
mKeyguardViewProperties =
new LockPatternKeyguardViewProperties(
new LockPatternUtils(mContext.getContentResolver()),
mUpdateMonitor);
mKeyguardViewManager = new KeyguardViewManager(
context, WindowManagerImpl.getDefault(), this,
mKeyguardViewProperties, mUpdateMonitor);
mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);
|
Methods Summary |
---|
private void | doKeyguard()Enable the keyguard if the settings are appropriate.
synchronized (this) {
// if another app is disabling us, don't show
if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
// note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
// for an occasional ugly flicker in this situation:
// 1) receive a call with the screen on (no keyguard) or make a call
// 2) screen times out
// 3) user hits key to turn screen back on
// instead, we reenable the keyguard when we know the screen is off and the call
// ends (see the broadcast receiver below)
// TODO: clean this up when we have better support at the window manager level
// for apps that wish to be on top of the keyguard
return;
}
// if the keyguard is already showing, don't bother
if (mKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
return;
}
// if the setup wizard hasn't run yet, don't show
final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
final SimCard.State state = mUpdateMonitor.getSimState();
final boolean lockedOrMissing = state.isPinLocked() || (state == SimCard.State.ABSENT);
if (!lockedOrMissing && !provisioned) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ " and the sim is not locked or missing");
return;
}
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
showLocked();
}
| private void | handleHide()Handle message sent by {@link #hideLocked()}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
// When we go away, tell the poewr manager to honor requests from userActivity.
mRealPowerManager.enableUserActivity(true);
mKeyguardViewManager.hide();
mShowing = false;
}
| private void | handleKeyguardDone()
if (DEBUG) Log.d(TAG, "handleKeyguardDone");
handleHide();
mPM.userActivity(SystemClock.uptimeMillis(), true);
mWakeLock.release();
mContext.sendBroadcast(mUserPresentIntent);
| private void | handleKeyguardDoneDrawing()
synchronized(this) {
if (false) Log.d(TAG, "handleKeyguardDoneDrawing");
if (mWaitingUntilKeyguardVisible) {
if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible");
mWaitingUntilKeyguardVisible = false;
notifyAll();
// there will usually be two of these sent, one as a timeout, and one
// as a result of the callback, so remove any remaining messages from
// the queue
mHandler.removeMessages(KEYGUARD_DONE_DRAWING);
}
}
| private void | handleNotifyScreenOff()Handle message sent by {@link #notifyScreenOffLocked()}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenOff");
mKeyguardViewManager.onScreenTurnedOff();
}
| private void | handleNotifyScreenOn()Handle message sent by {@link #notifyScreenOnLocked()}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenOn");
mKeyguardViewManager.onScreenTurnedOn();
}
| private void | handleReset()Handle message sent by {@link #resetStateLocked()}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleReset");
mKeyguardViewManager.reset();
}
| private void | handleShow()Handle message sent by {@link #showLocked}.
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleShow");
if (!mSystemReady) return;
// while we're showing, we control the wake state, so ask the power
// manager not to honor request for userActivity.
mRealPowerManager.enableUserActivity(false);
mCallback.onKeyguardShow();
mKeyguardViewManager.show();
mShowing = true;
}
| private void | handleTimeout(int seq)Handles the message sent by {@link #pokeWakelock}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleTimeout");
if (seq == mWakelockSequence) {
mWakeLock.release();
}
}
| private void | handleVerifyUnlock()Handle message sent by {@link #verifyUnlock}
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
mKeyguardViewManager.verifyUnlock();
mShowing = true;
}
| private void | handleWakeWhenReady(int keyCode)Handle message sent by {@link #wakeWhenReadyLocked(int)}
synchronized (KeyguardViewMediator.this) {
if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")");
// this should result in a call to 'poke wakelock' which will set a timeout
// on releasing the wakelock
mKeyguardViewManager.wakeWhenReadyTq(keyCode);
/**
* Now that the keyguard is ready and has poked the wake lock, we can
* release the handoff wakelock
*/
mWakeAndHandOff.release();
if (!mWakeLock.isHeld()) {
Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock");
}
}
| private void | hideLocked()Send message to keyguard telling it to hide itself
if (DEBUG) Log.d(TAG, "hideLocked");
Message msg = mHandler.obtainMessage(HIDE);
mHandler.sendMessage(msg);
| public boolean | isInputRestricted()Given the state of the keyguard, is the input restricted?
Input is restricted when the keyguard is showing, or when the keyguard
was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned();
| public boolean | isShowing()Is the keyguard currently showing?
return mShowing;
| private boolean | isWakeKeyWhenKeyguardShowing(int keyCode)
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_CAMERA:
return false;
}
return true;
| public void | keyguardDone(boolean authenticated){@inheritDoc}
synchronized (this) {
EventLog.writeEvent(70000, 2);
if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
mHandler.sendMessage(msg);
if (authenticated) {
mUpdateMonitor.clearFailedAttempts();
}
if (mExitSecureCallback != null) {
mExitSecureCallback.onKeyguardExitResult(authenticated);
mExitSecureCallback = null;
if (authenticated) {
// after succesfully exiting securely, no need to reshow
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
setStatusBarExpandable(true);
}
}
}
| public void | keyguardDoneDrawing(){@inheritDoc}
mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING);
| private void | notifyScreenOffLocked()Send a message to keyguard telling it the screen just turned on.
if (DEBUG) Log.d(TAG, "notifyScreenOffLocked");
mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF);
| private void | notifyScreenOnLocked()Send a message to keyguard telling it the screen just turned on.
if (DEBUG) Log.d(TAG, "notifyScreenOnLocked");
mHandler.sendEmptyMessage(NOTIFY_SCREEN_ON);
| public void | onKeyboardChange(boolean isKeyboardOpen){@inheritDoc}
mKeyboardOpen = isKeyboardOpen;
if (mKeyboardOpen && !mKeyguardViewProperties.isSecure()
&& mKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "bypassing keyguard on sliding open of keyboard with non-secure keyguard");
keyguardDone(true);
}
| public void | onOrientationChange(boolean inPortrait){@inheritDoc}
| public void | onScreenTurnedOff(int why)Called to let us know the screen was turned off.
synchronized (this) {
mScreenOn = false;
if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")");
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
mExitSecureCallback.onKeyguardExitResult(false);
mExitSecureCallback = null;
if (!mExternallyEnabled) {
hideLocked();
}
} else if (mShowing) {
notifyScreenOffLocked();
resetStateLocked();
} else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT) {
// if the screen turned off because of timeout, set an alarm
// to enable it a little bit later (i.e, give the user a chance
// to turn the screen back on within a certain window without
// having to unlock the screen)
long when = SystemClock.elapsedRealtime() + KEYGUARD_DELAY_MS;
Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
intent.putExtra("seq", mDelayedShowingSequence);
PendingIntent sender = PendingIntent.getBroadcast(mContext,
0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
sender);
if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = " + mDelayedShowingSequence);
} else {
doKeyguard();
}
}
| public void | onScreenTurnedOn()Let's us know the screen was turned on.
synchronized (this) {
mScreenOn = true;
mDelayedShowingSequence++;
if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
notifyScreenOnLocked();
}
| public void | onSimStateChanged(SimCard.State simState){@inheritDoc}
if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState);
switch (simState) {
case ABSENT:
// only force lock screen in case of missing sim if user hasn't
// gone through setup wizard
if (!mUpdateMonitor.isDeviceProvisioned()) {
if (!isShowing()) {
if (DEBUG) Log.d(TAG, "INTENT_VALUE_SIM_ABSENT and keygaurd isn't showing, we need "
+ "to show the keyguard since the device isn't provisioned yet.");
doKeyguard();
} else {
resetStateLocked();
}
}
break;
case PIN_REQUIRED:
case PUK_REQUIRED:
if (!isShowing()) {
if (DEBUG) Log.d(TAG, "INTENT_VALUE_SIM_LOCKED and keygaurd isn't showing, we need "
+ "to show the keyguard so the user can enter their sim pin");
doKeyguard();
} else {
resetStateLocked();
}
break;
case READY:
if (isShowing()) {
resetStateLocked();
}
break;
}
| public void | onSystemReady()Let us know that the system is ready after startup.
synchronized (this) {
if (DEBUG) Log.d(TAG, "onSystemReady");
mSystemReady = true;
doKeyguard();
}
| public boolean | onWakeKeyWhenKeyguardShowingTq(int keyCode)When a key is received when the screen is off and the keyguard is showing,
we need to decide whether to actually turn on the screen, and if so, tell
the keyguard to prepare itself and poke the wake lock when it is ready.
The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}.
Be sure not to take any action that takes a long time; any significant
action should be posted to a handler.
if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")");
if (isWakeKeyWhenKeyguardShowing(keyCode)) {
// give the keyguard view manager a chance to adjust the state of the
// keyguard based on the key that woke the device before poking
// the wake lock
wakeWhenReadyLocked(keyCode);
return true;
} else {
return false;
}
| public void | pokeWakelock(){@inheritDoc}
pokeWakelock(mKeyboardOpen ?
AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_MS : AWAKE_INTERVAL_DEFAULT_MS);
| public void | pokeWakelock(int holdMs){@inheritDoc}
synchronized (this) {
if (DBG_WAKE) Log.d(TAG, "pokeWakelock(" + holdMs + ")");
mWakeLock.acquire();
mHandler.removeMessages(TIMEOUT);
mWakelockSequence++;
Message msg = mHandler.obtainMessage(TIMEOUT, mWakelockSequence, 0);
mHandler.sendMessageDelayed(msg, holdMs);
}
| private void | resetStateLocked()Send message to keyguard telling it to reset its state.
if (DEBUG) Log.d(TAG, "resetStateLocked");
Message msg = mHandler.obtainMessage(RESET);
mHandler.sendMessage(msg);
| public void | setKeyguardEnabled(boolean enabled)Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide
a way for external stuff to override normal keyguard behavior. For instance
the phone app disables the keyguard when it receives incoming calls.
synchronized (this) {
if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
// we're in the process of handling a request to verify the user
// can get past the keyguard. ignore extraneous requests to disable / reenable
return;
}
// hiding keyguard that is showing, remember to reshow later
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
mNeedToReshowWhenReenabled = true;
setStatusBarExpandable(false);
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
// reenabled after previously hidden, reshow
if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ "status bar expansion");
mNeedToReshowWhenReenabled = false;
setStatusBarExpandable(true);
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
mExitSecureCallback.onKeyguardExitResult(false);
mExitSecureCallback = null;
resetStateLocked();
} else {
showLocked();
// block until we know the keygaurd is done drawing (and post a message
// to unblock us after a timeout so we don't risk blocking too long
// and causing an ANR).
mWaitingUntilKeyguardVisible = true;
mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
while (mWaitingUntilKeyguardVisible) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
}
}
| private void | setStatusBarExpandable(boolean isExpandable)
if (mStatusBarManager == null) {
mStatusBarManager =
(StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
}
mStatusBarManager.disable(isExpandable ? DISABLE_NONE : DISABLE_EXPAND);
| private void | showLocked()Send message to keyguard telling it to show itself
if (DEBUG) Log.d(TAG, "showLocked");
Message msg = mHandler.obtainMessage(SHOW);
mHandler.sendMessage(msg);
| public void | verifyUnlock(WindowManagerPolicy.OnKeyguardExitResult callback)
synchronized (this) {
if (DEBUG) Log.d(TAG, "verifyUnlock");
if (!mUpdateMonitor.isDeviceProvisioned()) {
// don't allow this api when the device isn't provisioned
if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
callback.onKeyguardExitResult(false);
} else if (mExternallyEnabled) {
// this only applies when the user has externally disabled the
// keyguard. this is unexpected and means the user is not
// using the api properly.
Log.w(TAG, "verifyUnlock called when not externally disabled");
callback.onKeyguardExitResult(false);
} else if (mExitSecureCallback != null) {
// already in progress with someone else
callback.onKeyguardExitResult(false);
} else {
mExitSecureCallback = callback;
verifyUnlockLocked();
}
}
| private void | verifyUnlockLocked()Send message to keyguard telling it to verify unlock
if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
mHandler.sendEmptyMessage(VERIFY_UNLOCK);
| private void | wakeWhenReadyLocked(int keyCode)Send message to keyguard telling it about a wake key so it can adjust
its state accordingly and then poke the wake lock when it is ready.
if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")");
/**
* acquire the handoff lock that will keep the cpu running. this will
* be released once the keyguard has set itself up and poked the other wakelock
* in {@link #handleWakeWhenReady(int)}
*/
mWakeAndHandOff.acquire();
Message msg = mHandler.obtainMessage(WAKE_WHEN_READY, keyCode, 0);
mHandler.sendMessage(msg);
|
|