1 /* 2 * Copyright (C) 2012 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.keyguard; 18 19 import com.android.internal.widget.LockPatternUtils; 20 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 21 import com.android.keyguard.KeyguardUpdateMonitor.DisplayClientState; 22 23 import android.app.ActivityManager; 24 import android.app.ActivityOptions; 25 import android.app.admin.DevicePolicyManager; 26 import android.appwidget.AppWidgetHost; 27 import android.appwidget.AppWidgetHostView; 28 import android.appwidget.AppWidgetManager; 29 import android.appwidget.AppWidgetProviderInfo; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentSender; 34 import android.content.pm.PackageManager.NameNotFoundException; 35 import android.content.res.Resources; 36 import android.graphics.Rect; 37 import android.media.RemoteControlClient; 38 import android.os.Bundle; 39 import android.os.Looper; 40 import android.os.Parcel; 41 import android.os.Parcelable; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.provider.Settings; 45 import android.util.AttributeSet; 46 import android.util.Log; 47 import android.util.Slog; 48 import android.view.LayoutInflater; 49 import android.view.MotionEvent; 50 import android.view.View; 51 import android.widget.RemoteViews.OnClickHandler; 52 53 import java.lang.ref.WeakReference; 54 55 public class KeyguardHostView extends KeyguardViewBase { 56 private static final String TAG = "KeyguardHostView"; 57 public static boolean DEBUG = KeyguardConstants.DEBUG; 58 public static boolean DEBUGXPORT = true; // debug music transport control 59 60 // Transport control states. 61 static final int TRANSPORT_GONE = 0; 62 static final int TRANSPORT_INVISIBLE = 1; 63 static final int TRANSPORT_VISIBLE = 2; 64 65 private int mTransportState = TRANSPORT_GONE; 66 67 // Found in KeyguardAppWidgetPickActivity.java 68 static final int APPWIDGET_HOST_ID = 0x4B455947; 69 private final int MAX_WIDGETS = 5; 70 71 private AppWidgetHost mAppWidgetHost; 72 private AppWidgetManager mAppWidgetManager; 73 private KeyguardWidgetPager mAppWidgetContainer; 74 // TODO remove transport control references, these don't exist anymore 75 private KeyguardTransportControlView mTransportControl; 76 private int mAppWidgetToShow; 77 78 protected int mFailedAttempts; 79 80 private KeyguardViewStateManager mViewStateManager; 81 82 private Rect mTempRect = new Rect(); 83 private int mDisabledFeatures; 84 private boolean mCameraDisabled; 85 private boolean mSafeModeEnabled; 86 private boolean mUserSetupCompleted; 87 88 // User for whom this host view was created. Final because we should never change the 89 // id without reconstructing an instance of KeyguardHostView. See note below... 90 private final int mUserId; 91 92 private KeyguardMultiUserSelectorView mKeyguardMultiUserSelectorView; 93 94 protected int mClientGeneration; 95 96 protected boolean mShowSecurityWhenReturn; 97 98 private final Rect mInsets = new Rect(); 99 100 private MyOnClickHandler mOnClickHandler = new MyOnClickHandler(this); 101 102 private Runnable mPostBootCompletedRunnable; 103 104 /*package*/ interface UserSwitcherCallback { 105 void hideSecurityView(int duration); 106 void showSecurityView(); 107 void showUnlockHint(); 108 void userActivity(); 109 } 110 111 interface TransportControlCallback { 112 void userActivity(); 113 } 114 115 public interface OnDismissAction { 116 /** 117 * @return true if the dismiss should be deferred 118 */ 119 boolean onDismiss(); 120 } 121 122 public KeyguardHostView(Context context) { 123 this(context, null); 124 } 125 126 public KeyguardHostView(Context context, AttributeSet attrs) { 127 super(context, attrs); 128 129 if (DEBUG) Log.e(TAG, "KeyguardHostView()"); 130 131 mLockPatternUtils = new LockPatternUtils(context); 132 133 // Note: This depends on KeyguardHostView getting reconstructed every time the 134 // user switches, since mUserId will be used for the entire session. 135 // Once created, keyguard should *never* re-use this instance with another user. 136 // In other words, mUserId should never change - hence it's marked final. 137 mUserId = mLockPatternUtils.getCurrentUser(); 138 139 DevicePolicyManager dpm = 140 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 141 if (dpm != null) { 142 mDisabledFeatures = getDisabledFeatures(dpm); 143 mCameraDisabled = dpm.getCameraDisabled(null); 144 } 145 146 mSafeModeEnabled = LockPatternUtils.isSafeModeEnabled(); 147 148 // These need to be created with the user context... 149 Context userContext = null; 150 try { 151 final String packageName = "system"; 152 userContext = mContext.createPackageContextAsUser(packageName, 0, 153 new UserHandle(mUserId)); 154 155 } catch (NameNotFoundException e) { 156 e.printStackTrace(); 157 // This should never happen, but it's better to have no widgets than to crash. 158 userContext = context; 159 } 160 161 mAppWidgetHost = new AppWidgetHost(userContext, APPWIDGET_HOST_ID, mOnClickHandler, 162 Looper.myLooper()); 163 164 mAppWidgetManager = AppWidgetManager.getInstance(userContext); 165 166 mViewStateManager = new KeyguardViewStateManager(this); 167 168 mUserSetupCompleted = Settings.Secure.getIntForUser(mContext.getContentResolver(), 169 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; 170 171 // Ensure we have the current state *before* we call showAppropriateWidgetPage() 172 getInitialTransportState(); 173 174 if (mSafeModeEnabled) { 175 Log.v(TAG, "Keyguard widgets disabled by safe mode"); 176 } 177 if ((mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0) { 178 Log.v(TAG, "Keyguard widgets disabled by DPM"); 179 } 180 if ((mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0) { 181 Log.v(TAG, "Keyguard secure camera disabled by DPM"); 182 } 183 } 184 185 private void getInitialTransportState() { 186 DisplayClientState dcs = KeyguardUpdateMonitor.getInstance(mContext) 187 .getCachedDisplayClientState(); 188 mTransportState = (dcs.clearing ? TRANSPORT_GONE : 189 (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); 190 191 if (DEBUGXPORT) Log.v(TAG, "Initial transport state: " 192 + mTransportState + ", pbstate=" + dcs.playbackState); 193 } 194 195 private void cleanupAppWidgetIds() { 196 if (mSafeModeEnabled || widgetsDisabled()) return; 197 198 // Clean up appWidgetIds that are bound to lockscreen, but not actually used 199 // This is only to clean up after another bug: we used to not call 200 // deleteAppWidgetId when a user manually deleted a widget in keyguard. This code 201 // shouldn't have to run more than once per user. AppWidgetProviders rely on callbacks 202 // that are triggered by deleteAppWidgetId, which is why we're doing this 203 int[] appWidgetIdsInKeyguardSettings = mLockPatternUtils.getAppWidgets(); 204 int[] appWidgetIdsBoundToHost = mAppWidgetHost.getAppWidgetIds(); 205 for (int i = 0; i < appWidgetIdsBoundToHost.length; i++) { 206 int appWidgetId = appWidgetIdsBoundToHost[i]; 207 if (!contains(appWidgetIdsInKeyguardSettings, appWidgetId)) { 208 Log.d(TAG, "Found a appWidgetId that's not being used by keyguard, deleting id " 209 + appWidgetId); 210 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 211 } 212 } 213 } 214 215 private static boolean contains(int[] array, int target) { 216 for (int value : array) { 217 if (value == target) { 218 return true; 219 } 220 } 221 return false; 222 } 223 224 private KeyguardUpdateMonitorCallback mUpdateMonitorCallbacks = 225 new KeyguardUpdateMonitorCallback() { 226 @Override 227 public void onBootCompleted() { 228 if (mPostBootCompletedRunnable != null) { 229 mPostBootCompletedRunnable.run(); 230 mPostBootCompletedRunnable = null; 231 } 232 } 233 @Override 234 public void onUserSwitchComplete(int userId) { 235 if (mKeyguardMultiUserSelectorView != null) { 236 mKeyguardMultiUserSelectorView.finalizeActiveUserView(true); 237 } 238 } 239 }; 240 241 private static final boolean isMusicPlaying(int playbackState) { 242 // This should agree with the list in AudioService.isPlaystateActive() 243 switch (playbackState) { 244 case RemoteControlClient.PLAYSTATE_PLAYING: 245 case RemoteControlClient.PLAYSTATE_BUFFERING: 246 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: 247 case RemoteControlClient.PLAYSTATE_REWINDING: 248 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: 249 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: 250 return true; 251 default: 252 return false; 253 } 254 } 255 256 private SlidingChallengeLayout mSlidingChallengeLayout; 257 private MultiPaneChallengeLayout mMultiPaneChallengeLayout; 258 259 @Override 260 public boolean onTouchEvent(MotionEvent ev) { 261 boolean result = super.onTouchEvent(ev); 262 mTempRect.set(0, 0, 0, 0); 263 offsetRectIntoDescendantCoords(getSecurityContainer(), mTempRect); 264 ev.offsetLocation(mTempRect.left, mTempRect.top); 265 result = getSecurityContainer().dispatchTouchEvent(ev) || result; 266 ev.offsetLocation(-mTempRect.left, -mTempRect.top); 267 return result; 268 } 269 270 private int getWidgetPosition(int id) { 271 final KeyguardWidgetPager appWidgetContainer = mAppWidgetContainer; 272 final int children = appWidgetContainer.getChildCount(); 273 for (int i = 0; i < children; i++) { 274 final View content = appWidgetContainer.getWidgetPageAt(i).getContent(); 275 if (content != null && content.getId() == id) { 276 return i; 277 } else if (content == null) { 278 // Attempt to track down bug #8886916 279 Log.w(TAG, "*** Null content at " + "i=" + i + ",id=" + id + ",N=" + children); 280 } 281 } 282 return -1; 283 } 284 285 @Override 286 protected void onFinishInflate() { 287 super.onFinishInflate(); 288 289 // Grab instances of and make any necessary changes to the main layouts. Create 290 // view state manager and wire up necessary listeners / callbacks. 291 View deleteDropTarget = findViewById(R.id.keyguard_widget_pager_delete_target); 292 mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container); 293 mAppWidgetContainer.setVisibility(VISIBLE); 294 mAppWidgetContainer.setCallbacks(mWidgetCallbacks); 295 mAppWidgetContainer.setDeleteDropTarget(deleteDropTarget); 296 mAppWidgetContainer.setMinScale(0.5f); 297 298 mSlidingChallengeLayout = (SlidingChallengeLayout) findViewById(R.id.sliding_layout); 299 if (mSlidingChallengeLayout != null) { 300 mSlidingChallengeLayout.setOnChallengeScrolledListener(mViewStateManager); 301 } 302 mAppWidgetContainer.setViewStateManager(mViewStateManager); 303 mAppWidgetContainer.setLockPatternUtils(mLockPatternUtils); 304 305 mMultiPaneChallengeLayout = 306 (MultiPaneChallengeLayout) findViewById(R.id.multi_pane_challenge); 307 ChallengeLayout challenge = mSlidingChallengeLayout != null ? mSlidingChallengeLayout : 308 mMultiPaneChallengeLayout; 309 challenge.setOnBouncerStateChangedListener(mViewStateManager); 310 mAppWidgetContainer.setBouncerAnimationDuration(challenge.getBouncerAnimationDuration()); 311 mViewStateManager.setPagedView(mAppWidgetContainer); 312 mViewStateManager.setChallengeLayout(challenge); 313 314 mViewStateManager.setSecurityViewContainer(getSecurityContainer()); 315 316 if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { 317 updateAndAddWidgets(); 318 } else { 319 // We can't add widgets until after boot completes because AppWidgetHost may try 320 // to contact the providers. Do it later. 321 mPostBootCompletedRunnable = new Runnable() { 322 @Override 323 public void run() { 324 updateAndAddWidgets(); 325 } 326 }; 327 } 328 329 getSecurityContainer().updateSecurityViews(mViewStateManager.isBouncing()); 330 enableUserSelectorIfNecessary(); 331 } 332 333 private void updateAndAddWidgets() { 334 cleanupAppWidgetIds(); 335 addDefaultWidgets(); 336 addWidgetsFromSettings(); 337 maybeEnableAddButton(); 338 checkAppWidgetConsistency(); 339 340 // Don't let the user drag the challenge down if widgets are disabled. 341 if (mSlidingChallengeLayout != null) { 342 mSlidingChallengeLayout.setEnableChallengeDragging(!widgetsDisabled()); 343 } 344 345 // Select the appropriate page 346 mSwitchPageRunnable.run(); 347 348 // This needs to be called after the pages are all added. 349 mViewStateManager.showUsabilityHints(); 350 } 351 352 private void maybeEnableAddButton() { 353 if (!shouldEnableAddWidget()) { 354 mAppWidgetContainer.setAddWidgetEnabled(false); 355 } 356 } 357 358 private boolean shouldEnableAddWidget() { 359 return numWidgets() < MAX_WIDGETS && mUserSetupCompleted; 360 } 361 362 @Override 363 public boolean dismiss(boolean authenticated) { 364 boolean finished = super.dismiss(authenticated); 365 if (!finished) { 366 mViewStateManager.showBouncer(true); 367 368 // Enter full screen mode if we're in SIM or Account screen 369 SecurityMode securityMode = getSecurityContainer().getSecurityMode(); 370 boolean isFullScreen = getResources().getBoolean(R.bool.kg_sim_puk_account_full_screen); 371 boolean isSimOrAccount = securityMode == SecurityMode.SimPin 372 || securityMode == SecurityMode.SimPuk 373 || securityMode == SecurityMode.Account; 374 mAppWidgetContainer.setVisibility( 375 isSimOrAccount && isFullScreen ? View.GONE : View.VISIBLE); 376 377 // Don't show camera or search in navbar when SIM or Account screen is showing 378 setSystemUiVisibility(isSimOrAccount ? 379 (getSystemUiVisibility() | View.STATUS_BAR_DISABLE_SEARCH) 380 : (getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_SEARCH)); 381 382 if (mSlidingChallengeLayout != null) { 383 mSlidingChallengeLayout.setChallengeInteractive(!isFullScreen); 384 } 385 } 386 return finished; 387 } 388 389 private int getDisabledFeatures(DevicePolicyManager dpm) { 390 int disabledFeatures = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; 391 if (dpm != null) { 392 final int currentUser = mLockPatternUtils.getCurrentUser(); 393 disabledFeatures = dpm.getKeyguardDisabledFeatures(null, currentUser); 394 } 395 return disabledFeatures; 396 } 397 398 private boolean widgetsDisabled() { 399 boolean disabledByLowRamDevice = ActivityManager.isLowRamDeviceStatic(); 400 boolean disabledByDpm = 401 (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0; 402 boolean disabledByUser = !mLockPatternUtils.getWidgetsEnabled(); 403 return disabledByLowRamDevice || disabledByDpm || disabledByUser; 404 } 405 406 private boolean cameraDisabledByDpm() { 407 return mCameraDisabled 408 || (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0; 409 } 410 411 @Override 412 public void setLockPatternUtils(LockPatternUtils utils) { 413 super.setLockPatternUtils(utils); 414 getSecurityContainer().updateSecurityViews(mViewStateManager.isBouncing()); 415 } 416 417 @Override 418 protected void onAttachedToWindow() { 419 super.onAttachedToWindow(); 420 mAppWidgetHost.startListening(); 421 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallbacks); 422 } 423 424 @Override 425 protected void onDetachedFromWindow() { 426 super.onDetachedFromWindow(); 427 mAppWidgetHost.stopListening(); 428 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallbacks); 429 } 430 431 void addWidget(AppWidgetHostView view, int pageIndex) { 432 mAppWidgetContainer.addWidget(view, pageIndex); 433 } 434 435 private KeyguardWidgetPager.Callbacks mWidgetCallbacks 436 = new KeyguardWidgetPager.Callbacks() { 437 @Override 438 public void userActivity() { 439 KeyguardHostView.this.userActivity(); 440 } 441 442 @Override 443 public void onUserActivityTimeoutChanged() { 444 KeyguardHostView.this.onUserActivityTimeoutChanged(); 445 } 446 447 @Override 448 public void onAddView(View v) { 449 if (!shouldEnableAddWidget()) { 450 mAppWidgetContainer.setAddWidgetEnabled(false); 451 } 452 } 453 454 @Override 455 public void onRemoveView(View v, boolean deletePermanently) { 456 if (deletePermanently) { 457 final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 458 if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && 459 appWidgetId != LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) { 460 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 461 } 462 } 463 } 464 465 @Override 466 public void onRemoveViewAnimationCompleted() { 467 if (shouldEnableAddWidget()) { 468 mAppWidgetContainer.setAddWidgetEnabled(true); 469 } 470 } 471 }; 472 473 @Override 474 public void onUserSwitching(boolean switching) { 475 if (!switching && mKeyguardMultiUserSelectorView != null) { 476 mKeyguardMultiUserSelectorView.finalizeActiveUserView(false); 477 } 478 } 479 480 public void userActivity() { 481 if (mViewMediatorCallback != null) { 482 mViewMediatorCallback.userActivity(); 483 } 484 } 485 486 public void onUserActivityTimeoutChanged() { 487 if (mViewMediatorCallback != null) { 488 mViewMediatorCallback.onUserActivityTimeoutChanged(); 489 } 490 } 491 492 @Override 493 public long getUserActivityTimeout() { 494 // Currently only considering user activity timeouts needed by widgets. 495 // Could also take into account longer timeouts for certain security views. 496 if (mAppWidgetContainer != null) { 497 return mAppWidgetContainer.getUserActivityTimeout(); 498 } 499 return -1; 500 } 501 502 private static class MyOnClickHandler extends OnClickHandler { 503 504 // weak reference to the hostView to avoid keeping a live reference 505 // due to Binder GC linkages to AppWidgetHost. By the same token, 506 // this click handler should not keep references to any large 507 // objects. 508 WeakReference<KeyguardHostView> mKeyguardHostView; 509 510 MyOnClickHandler(KeyguardHostView hostView) { 511 mKeyguardHostView = new WeakReference<KeyguardHostView>(hostView); 512 } 513 514 @Override 515 public boolean onClickHandler(final View view, 516 final android.app.PendingIntent pendingIntent, 517 final Intent fillInIntent) { 518 KeyguardHostView hostView = mKeyguardHostView.get(); 519 if (hostView == null) { 520 return false; 521 } 522 if (pendingIntent.isActivity()) { 523 hostView.setOnDismissAction(new OnDismissAction() { 524 public boolean onDismiss() { 525 try { 526 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 527 Context context = view.getContext(); 528 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, 529 0, 0, 530 view.getMeasuredWidth(), view.getMeasuredHeight()); 531 context.startIntentSender( 532 pendingIntent.getIntentSender(), fillInIntent, 533 Intent.FLAG_ACTIVITY_NEW_TASK, 534 Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); 535 } catch (IntentSender.SendIntentException e) { 536 android.util.Log.e(TAG, "Cannot send pending intent: ", e); 537 } catch (Exception e) { 538 android.util.Log.e(TAG, "Cannot send pending intent due to " + 539 "unknown exception: ", e); 540 } 541 return false; 542 } 543 }); 544 545 if (hostView.mViewStateManager.isChallengeShowing()) { 546 hostView.mViewStateManager.showBouncer(true); 547 } else { 548 hostView.dismiss(); 549 } 550 return true; 551 } else { 552 return super.onClickHandler(view, pendingIntent, fillInIntent); 553 } 554 }; 555 }; 556 557 @Override 558 public void onResume() { 559 super.onResume(); 560 if (mViewStateManager != null) { 561 mViewStateManager.showUsabilityHints(); 562 } 563 } 564 565 @Override 566 public void onPause() { 567 super.onPause(); 568 // We use mAppWidgetToShow to show a particular widget after you add it-- once the screen 569 // turns off we reset that behavior 570 clearAppWidgetToShow(); 571 if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { 572 checkAppWidgetConsistency(); 573 } 574 CameraWidgetFrame cameraPage = findCameraPage(); 575 if (cameraPage != null) { 576 cameraPage.onScreenTurnedOff(); 577 } 578 } 579 580 public void clearAppWidgetToShow() { 581 mAppWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; 582 } 583 584 private boolean addWidget(int appId, int pageIndex, boolean updateDbIfFailed) { 585 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId); 586 if (appWidgetInfo != null) { 587 AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo); 588 addWidget(view, pageIndex); 589 return true; 590 } else { 591 if (updateDbIfFailed) { 592 Log.w(TAG, "*** AppWidgetInfo for app widget id " + appId + " was null for user" 593 + mUserId + ", deleting"); 594 mAppWidgetHost.deleteAppWidgetId(appId); 595 mLockPatternUtils.removeAppWidget(appId); 596 } 597 return false; 598 } 599 } 600 601 private final CameraWidgetFrame.Callbacks mCameraWidgetCallbacks = 602 new CameraWidgetFrame.Callbacks() { 603 @Override 604 public void onLaunchingCamera() { 605 setSliderHandleAlpha(0); 606 } 607 608 @Override 609 public void onCameraLaunchedSuccessfully() { 610 if (mAppWidgetContainer.isCameraPage(mAppWidgetContainer.getCurrentPage())) { 611 mAppWidgetContainer.scrollLeft(); 612 } 613 setSliderHandleAlpha(1); 614 mShowSecurityWhenReturn = true; 615 } 616 617 @Override 618 public void onCameraLaunchedUnsuccessfully() { 619 setSliderHandleAlpha(1); 620 } 621 622 private void setSliderHandleAlpha(float alpha) { 623 SlidingChallengeLayout slider = 624 (SlidingChallengeLayout) findViewById(R.id.sliding_layout); 625 if (slider != null) { 626 slider.setHandleAlpha(alpha); 627 } 628 } 629 }; 630 631 private int numWidgets() { 632 final int childCount = mAppWidgetContainer.getChildCount(); 633 int widgetCount = 0; 634 for (int i = 0; i < childCount; i++) { 635 if (mAppWidgetContainer.isWidgetPage(i)) { 636 widgetCount++; 637 } 638 } 639 return widgetCount; 640 } 641 642 private void addDefaultWidgets() { 643 if (!mSafeModeEnabled && !widgetsDisabled()) { 644 LayoutInflater inflater = LayoutInflater.from(mContext); 645 View addWidget = inflater.inflate(R.layout.keyguard_add_widget, this, false); 646 mAppWidgetContainer.addWidget(addWidget, 0); 647 View addWidgetButton = addWidget.findViewById(R.id.keyguard_add_widget_view); 648 addWidgetButton.setOnClickListener(new OnClickListener() { 649 @Override 650 public void onClick(View v) { 651 // Pass in an invalid widget id... the picker will allocate an ID for us 652 getActivityLauncher().launchWidgetPicker(AppWidgetManager.INVALID_APPWIDGET_ID); 653 } 654 }); 655 } 656 657 // We currently disable cameras in safe mode because we support loading 3rd party 658 // cameras we can't trust. TODO: plumb safe mode into camera creation code and only 659 // inflate system-provided camera? 660 if (!mSafeModeEnabled && !cameraDisabledByDpm() && mUserSetupCompleted 661 && mContext.getResources().getBoolean(R.bool.kg_enable_camera_default_widget)) { 662 View cameraWidget = CameraWidgetFrame.create(mContext, mCameraWidgetCallbacks, 663 getActivityLauncher()); 664 if (cameraWidget != null) { 665 mAppWidgetContainer.addWidget(cameraWidget); 666 } 667 } 668 } 669 670 /** 671 * Create KeyguardTransportControlView on demand. 672 * @return 673 */ 674 private KeyguardTransportControlView getOrCreateTransportControl() { 675 if (mTransportControl == null) { 676 LayoutInflater inflater = LayoutInflater.from(mContext); 677 mTransportControl = (KeyguardTransportControlView) 678 inflater.inflate(R.layout.keyguard_transport_control_view, this, false); 679 mTransportControl.setTransportControlCallback(new TransportControlCallback() { 680 public void userActivity() { 681 mViewMediatorCallback.userActivity(); 682 } 683 }); 684 } 685 return mTransportControl; 686 } 687 688 private int getInsertPageIndex() { 689 View addWidget = mAppWidgetContainer.findViewById(R.id.keyguard_add_widget); 690 int insertionIndex = mAppWidgetContainer.indexOfChild(addWidget); 691 if (insertionIndex < 0) { 692 insertionIndex = 0; // no add widget page found 693 } else { 694 insertionIndex++; // place after add widget 695 } 696 return insertionIndex; 697 } 698 699 private void addDefaultStatusWidget(int index) { 700 LayoutInflater inflater = LayoutInflater.from(mContext); 701 View statusWidget = inflater.inflate(R.layout.keyguard_status_view, null, true); 702 mAppWidgetContainer.addWidget(statusWidget, index); 703 } 704 705 private void addWidgetsFromSettings() { 706 if (mSafeModeEnabled || widgetsDisabled()) { 707 addDefaultStatusWidget(0); 708 return; 709 } 710 711 int insertionIndex = getInsertPageIndex(); 712 713 // Add user-selected widget 714 final int[] widgets = mLockPatternUtils.getAppWidgets(); 715 716 if (widgets == null) { 717 Log.d(TAG, "Problem reading widgets"); 718 } else { 719 for (int i = widgets.length -1; i >= 0; i--) { 720 if (widgets[i] == LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) { 721 addDefaultStatusWidget(insertionIndex); 722 } else { 723 // We add the widgets from left to right, starting after the first page after 724 // the add page. We count down, since the order will be persisted from right 725 // to left, starting after camera. 726 addWidget(widgets[i], insertionIndex, true); 727 } 728 } 729 } 730 } 731 732 private int allocateIdForDefaultAppWidget() { 733 int appWidgetId; 734 Resources res = getContext().getResources(); 735 ComponentName defaultAppWidget = new ComponentName( 736 res.getString(R.string.widget_default_package_name), 737 res.getString(R.string.widget_default_class_name)); 738 739 // Note: we don't support configuring the widget 740 appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 741 742 try { 743 mAppWidgetManager.bindAppWidgetId(appWidgetId, defaultAppWidget); 744 } catch (IllegalArgumentException e) { 745 Log.e(TAG, "Error when trying to bind default AppWidget: " + e); 746 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 747 appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 748 } 749 return appWidgetId; 750 } 751 752 public void checkAppWidgetConsistency() { 753 final int childCount = mAppWidgetContainer.getChildCount(); 754 boolean widgetPageExists = false; 755 for (int i = 0; i < childCount; i++) { 756 if (mAppWidgetContainer.isWidgetPage(i)) { 757 widgetPageExists = true; 758 break; 759 } 760 } 761 if (!widgetPageExists) { 762 final int insertPageIndex = getInsertPageIndex(); 763 764 final boolean userAddedWidgetsEnabled = !widgetsDisabled(); 765 766 boolean addedDefaultAppWidget = false; 767 768 if (!mSafeModeEnabled) { 769 if (userAddedWidgetsEnabled) { 770 int appWidgetId = allocateIdForDefaultAppWidget(); 771 if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 772 addedDefaultAppWidget = addWidget(appWidgetId, insertPageIndex, true); 773 } 774 } else { 775 // note: even if widgetsDisabledByDpm() returns true, we still bind/create 776 // the default appwidget if possible 777 int appWidgetId = mLockPatternUtils.getFallbackAppWidgetId(); 778 if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { 779 appWidgetId = allocateIdForDefaultAppWidget(); 780 if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 781 mLockPatternUtils.writeFallbackAppWidgetId(appWidgetId); 782 } 783 } 784 if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 785 addedDefaultAppWidget = addWidget(appWidgetId, insertPageIndex, false); 786 if (!addedDefaultAppWidget) { 787 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 788 mLockPatternUtils.writeFallbackAppWidgetId( 789 AppWidgetManager.INVALID_APPWIDGET_ID); 790 } 791 } 792 } 793 } 794 795 // Use the built-in status/clock view if we can't inflate the default widget 796 if (!addedDefaultAppWidget) { 797 addDefaultStatusWidget(insertPageIndex); 798 } 799 800 // trigger DB updates only if user-added widgets are enabled 801 if (!mSafeModeEnabled && userAddedWidgetsEnabled) { 802 mAppWidgetContainer.onAddView( 803 mAppWidgetContainer.getChildAt(insertPageIndex), insertPageIndex); 804 } 805 } 806 } 807 808 private final Runnable mSwitchPageRunnable = new Runnable() { 809 @Override 810 public void run() { 811 showAppropriateWidgetPage(); 812 } 813 }; 814 815 static class SavedState extends BaseSavedState { 816 int transportState; 817 int appWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; 818 Rect insets = new Rect(); 819 820 SavedState(Parcelable superState) { 821 super(superState); 822 } 823 824 private SavedState(Parcel in) { 825 super(in); 826 this.transportState = in.readInt(); 827 this.appWidgetToShow = in.readInt(); 828 this.insets = in.readParcelable(null); 829 } 830 831 @Override 832 public void writeToParcel(Parcel out, int flags) { 833 super.writeToParcel(out, flags); 834 out.writeInt(this.transportState); 835 out.writeInt(this.appWidgetToShow); 836 out.writeParcelable(insets, 0); 837 } 838 839 public static final Parcelable.Creator<SavedState> CREATOR 840 = new Parcelable.Creator<SavedState>() { 841 public SavedState createFromParcel(Parcel in) { 842 return new SavedState(in); 843 } 844 845 public SavedState[] newArray(int size) { 846 return new SavedState[size]; 847 } 848 }; 849 } 850 851 @Override 852 public Parcelable onSaveInstanceState() { 853 if (DEBUG) Log.d(TAG, "onSaveInstanceState, tstate=" + mTransportState); 854 Parcelable superState = super.onSaveInstanceState(); 855 SavedState ss = new SavedState(superState); 856 // If the transport is showing, force it to show it on restore. 857 final boolean showing = mTransportControl != null 858 && mAppWidgetContainer.getWidgetPageIndex(mTransportControl) >= 0; 859 ss.transportState = showing ? TRANSPORT_VISIBLE : mTransportState; 860 ss.appWidgetToShow = mAppWidgetToShow; 861 ss.insets.set(mInsets); 862 return ss; 863 } 864 865 @Override 866 public void onRestoreInstanceState(Parcelable state) { 867 if (!(state instanceof SavedState)) { 868 super.onRestoreInstanceState(state); 869 return; 870 } 871 SavedState ss = (SavedState) state; 872 super.onRestoreInstanceState(ss.getSuperState()); 873 mTransportState = (ss.transportState); 874 mAppWidgetToShow = ss.appWidgetToShow; 875 setInsets(ss.insets); 876 if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState); 877 mSwitchPageRunnable.run(); 878 } 879 880 @Override 881 protected boolean fitSystemWindows(Rect insets) { 882 setInsets(insets); 883 return true; 884 } 885 886 private void setInsets(Rect insets) { 887 mInsets.set(insets); 888 if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setInsets(mInsets); 889 if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setInsets(mInsets); 890 891 final CameraWidgetFrame cameraWidget = findCameraPage(); 892 if (cameraWidget != null) cameraWidget.setInsets(mInsets); 893 } 894 895 @Override 896 public void onWindowFocusChanged(boolean hasWindowFocus) { 897 super.onWindowFocusChanged(hasWindowFocus); 898 if (DEBUG) Log.d(TAG, "Window is " + (hasWindowFocus ? "focused" : "unfocused")); 899 if (hasWindowFocus && mShowSecurityWhenReturn) { 900 SlidingChallengeLayout slider = 901 (SlidingChallengeLayout) findViewById(R.id.sliding_layout); 902 if (slider != null) { 903 slider.setHandleAlpha(1); 904 slider.showChallenge(true); 905 } 906 mShowSecurityWhenReturn = false; 907 } 908 } 909 910 private void showAppropriateWidgetPage() { 911 final int state = mTransportState; 912 final boolean transportAdded = ensureTransportPresentOrRemoved(state); 913 final int pageToShow = getAppropriateWidgetPage(state); 914 if (!transportAdded) { 915 mAppWidgetContainer.setCurrentPage(pageToShow); 916 } else if (state == TRANSPORT_VISIBLE) { 917 // If the transport was just added, we need to wait for layout to happen before 918 // we can set the current page. 919 post(new Runnable() { 920 @Override 921 public void run() { 922 mAppWidgetContainer.setCurrentPage(pageToShow); 923 } 924 }); 925 } 926 } 927 928 /** 929 * Examines the current state and adds the transport to the widget pager when the state changes. 930 * 931 * Showing the initial transport and keeping it around is a bit tricky because the signals 932 * coming from music players aren't always clear. Here's how the states are handled: 933 * 934 * {@link TRANSPORT_GONE} means we have no reason to show the transport - remove it if present. 935 * 936 * {@link TRANSPORT_INVISIBLE} means we have potential to show the transport because a music 937 * player is registered but not currently playing music (or we don't know the state yet). The 938 * code adds it conditionally on play state. 939 * 940 * {@link #TRANSPORT_VISIBLE} means a music player is active and transport should be showing. 941 * 942 * Once the transport is showing, we always show it until keyguard is dismissed. This state is 943 * maintained by onSave/RestoreInstanceState(). This state is cleared in 944 * {@link KeyguardViewManager#hide} when keyguard is dismissed, which causes the transport to be 945 * gone when keyguard is restarted until we get an update with the current state. 946 * 947 * @param state 948 */ 949 private boolean ensureTransportPresentOrRemoved(int state) { 950 final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1; 951 final boolean visible = state == TRANSPORT_VISIBLE; 952 final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state); 953 if (!showing && (visible || shouldBeVisible)) { 954 // insert to left of camera if it exists, otherwise after right-most widget 955 int lastWidget = mAppWidgetContainer.getChildCount() - 1; 956 int position = 0; // handle no widget case 957 if (lastWidget >= 0) { 958 position = mAppWidgetContainer.isCameraPage(lastWidget) ? 959 lastWidget : lastWidget + 1; 960 } 961 if (DEBUGXPORT) Log.v(TAG, "add transport at " + position); 962 mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position); 963 return true; 964 } else if (showing && state == TRANSPORT_GONE) { 965 if (DEBUGXPORT) Log.v(TAG, "remove transport"); 966 mAppWidgetContainer.removeWidget(getOrCreateTransportControl()); 967 mTransportControl = null; 968 KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null); 969 } 970 return false; 971 } 972 973 private CameraWidgetFrame findCameraPage() { 974 for (int i = mAppWidgetContainer.getChildCount() - 1; i >= 0; i--) { 975 if (mAppWidgetContainer.isCameraPage(i)) { 976 return (CameraWidgetFrame) mAppWidgetContainer.getChildAt(i); 977 } 978 } 979 return null; 980 } 981 982 boolean isMusicPage(int pageIndex) { 983 return pageIndex >= 0 && pageIndex == getWidgetPosition(R.id.keyguard_transport_control); 984 } 985 986 private int getAppropriateWidgetPage(int musicTransportState) { 987 // assumes at least one widget (besides camera + add) 988 if (mAppWidgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { 989 final int childCount = mAppWidgetContainer.getChildCount(); 990 for (int i = 0; i < childCount; i++) { 991 if (mAppWidgetContainer.getWidgetPageAt(i).getContentAppWidgetId() 992 == mAppWidgetToShow) { 993 return i; 994 } 995 } 996 mAppWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; 997 } 998 // if music playing, show transport 999 if (musicTransportState == TRANSPORT_VISIBLE) { 1000 if (DEBUG) Log.d(TAG, "Music playing, show transport"); 1001 return mAppWidgetContainer.getWidgetPageIndex(getOrCreateTransportControl()); 1002 } 1003 1004 // else show the right-most widget (except for camera) 1005 int rightMost = mAppWidgetContainer.getChildCount() - 1; 1006 if (mAppWidgetContainer.isCameraPage(rightMost)) { 1007 rightMost--; 1008 } 1009 if (DEBUG) Log.d(TAG, "Show right-most page " + rightMost); 1010 return rightMost; 1011 } 1012 1013 private void enableUserSelectorIfNecessary() { 1014 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 1015 if (um == null) { 1016 Throwable t = new Throwable(); 1017 t.fillInStackTrace(); 1018 Log.e(TAG, "user service is null.", t); 1019 return; 1020 } 1021 1022 // if there are multiple users, we need to enable to multi-user switcher 1023 if (!um.isUserSwitcherEnabled()) { 1024 return; 1025 } 1026 1027 final View multiUserView = findViewById(R.id.keyguard_user_selector); 1028 if (multiUserView == null) { 1029 if (DEBUG) Log.d(TAG, "can't find user_selector in layout."); 1030 return; 1031 } 1032 1033 if (multiUserView instanceof KeyguardMultiUserSelectorView) { 1034 mKeyguardMultiUserSelectorView = (KeyguardMultiUserSelectorView) multiUserView; 1035 mKeyguardMultiUserSelectorView.setVisibility(View.VISIBLE); 1036 mKeyguardMultiUserSelectorView.addUsers(um.getUsers(true)); 1037 UserSwitcherCallback callback = new UserSwitcherCallback() { 1038 @Override 1039 public void hideSecurityView(int duration) { 1040 getSecurityContainer().animate().alpha(0).setDuration(duration); 1041 } 1042 1043 @Override 1044 public void showSecurityView() { 1045 getSecurityContainer().setAlpha(1.0f); 1046 } 1047 1048 @Override 1049 public void showUnlockHint() { 1050 if (getSecurityContainer() != null) { 1051 getSecurityContainer().showUsabilityHint(); 1052 } 1053 } 1054 1055 @Override 1056 public void userActivity() { 1057 if (mViewMediatorCallback != null) { 1058 mViewMediatorCallback.userActivity(); 1059 } 1060 } 1061 }; 1062 mKeyguardMultiUserSelectorView.setCallback(callback); 1063 } else { 1064 Throwable t = new Throwable(); 1065 t.fillInStackTrace(); 1066 if (multiUserView == null) { 1067 Log.e(TAG, "could not find the user_selector.", t); 1068 } else { 1069 Log.e(TAG, "user_selector is the wrong type.", t); 1070 } 1071 } 1072 } 1073 1074 @Override 1075 public void cleanUp() { 1076 // Make sure we let go of all widgets and their package contexts promptly. If we don't do 1077 // this, and the associated application is uninstalled, it can cause a soft reboot. 1078 int count = mAppWidgetContainer.getChildCount(); 1079 for (int i = 0; i < count; i++) { 1080 KeyguardWidgetFrame frame = mAppWidgetContainer.getWidgetPageAt(i); 1081 frame.removeAllViews(); 1082 } 1083 getSecurityContainer().onPause(); // clean up any actions in progress 1084 } 1085 1086 public void goToWidget(int appWidgetId) { 1087 mAppWidgetToShow = appWidgetId; 1088 mSwitchPageRunnable.run(); 1089 } 1090 1091 @Override 1092 protected void showBouncer(boolean show) { 1093 super.showBouncer(show); 1094 mViewStateManager.showBouncer(show); 1095 } 1096 1097 @Override 1098 public void onExternalMotionEvent(MotionEvent event) { 1099 mAppWidgetContainer.handleExternalCameraEvent(event); 1100 } 1101 1102 @Override 1103 protected void onCreateOptions(Bundle options) { 1104 if (options != null) { 1105 int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, 1106 AppWidgetManager.INVALID_APPWIDGET_ID); 1107 if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { 1108 goToWidget(widgetToShow); 1109 } 1110 } 1111 } 1112 1113 } 1114