| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.keyguard; |
| |
| import android.nfc.NfcUnlock; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.keyguard.KeyguardSecurityModel.SecurityMode; |
| import com.android.keyguard.KeyguardUpdateMonitor.DisplayClientState; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.app.SearchManager; |
| import android.app.admin.DevicePolicyManager; |
| import android.appwidget.AppWidgetHost; |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetManager; |
| import android.appwidget.AppWidgetProviderInfo; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.UserInfo; |
| import android.content.res.Resources; |
| import android.graphics.Rect; |
| import android.media.RemoteControlClient; |
| import android.os.Looper; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.widget.RemoteViews.OnClickHandler; |
| |
| import java.io.File; |
| import java.lang.ref.WeakReference; |
| import java.util.List; |
| |
| public class KeyguardHostView extends KeyguardViewBase { |
| private static final String TAG = "KeyguardHostView"; |
| public static boolean DEBUG = KeyguardViewMediator.DEBUG; |
| public static boolean DEBUGXPORT = true; // debug music transport control |
| |
| // Transport control states. |
| static final int TRANSPORT_GONE = 0; |
| static final int TRANSPORT_INVISIBLE = 1; |
| static final int TRANSPORT_VISIBLE = 2; |
| |
| private int mTransportState = TRANSPORT_GONE; |
| |
| // Found in KeyguardAppWidgetPickActivity.java |
| static final int APPWIDGET_HOST_ID = 0x4B455947; |
| private final int MAX_WIDGETS = 5; |
| |
| private AppWidgetHost mAppWidgetHost; |
| private AppWidgetManager mAppWidgetManager; |
| private KeyguardWidgetPager mAppWidgetContainer; |
| private KeyguardTransportControlView mTransportControl; |
| private int mAppWidgetToShow; |
| |
| protected int mFailedAttempts; |
| |
| private KeyguardViewStateManager mViewStateManager; |
| |
| private Rect mTempRect = new Rect(); |
| private int mDisabledFeatures; |
| private boolean mCameraDisabled; |
| private boolean mSafeModeEnabled; |
| private boolean mUserSetupCompleted; |
| |
| // User for whom this host view was created. Final because we should never change the |
| // id without reconstructing an instance of KeyguardHostView. See note below... |
| private final int mUserId; |
| |
| private KeyguardMultiUserSelectorView mKeyguardMultiUserSelectorView; |
| |
| protected int mClientGeneration; |
| |
| protected boolean mShowSecurityWhenReturn; |
| |
| private final Rect mInsets = new Rect(); |
| |
| private MyOnClickHandler mOnClickHandler = new MyOnClickHandler(this); |
| |
| private Runnable mPostBootCompletedRunnable; |
| |
| /*package*/ interface UserSwitcherCallback { |
| void hideSecurityView(int duration); |
| void showSecurityView(); |
| void showUnlockHint(); |
| void userActivity(); |
| } |
| |
| interface TransportControlCallback { |
| void userActivity(); |
| } |
| |
| /*package*/ interface OnDismissAction { |
| /* returns true if the dismiss should be deferred */ |
| boolean onDismiss(); |
| } |
| |
| public KeyguardHostView(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardHostView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| if (DEBUG) Log.e(TAG, "KeyguardHostView()"); |
| |
| mLockPatternUtils = new LockPatternUtils(context); |
| |
| // Note: This depends on KeyguardHostView getting reconstructed every time the |
| // user switches, since mUserId will be used for the entire session. |
| // Once created, keyguard should *never* re-use this instance with another user. |
| // In other words, mUserId should never change - hence it's marked final. |
| mUserId = mLockPatternUtils.getCurrentUser(); |
| |
| DevicePolicyManager dpm = |
| (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); |
| if (dpm != null) { |
| mDisabledFeatures = getDisabledFeatures(dpm); |
| mCameraDisabled = dpm.getCameraDisabled(null); |
| } |
| |
| mSafeModeEnabled = LockPatternUtils.isSafeModeEnabled(); |
| |
| // These need to be created with the user context... |
| Context userContext = null; |
| try { |
| final String packageName = "system"; |
| userContext = mContext.createPackageContextAsUser(packageName, 0, |
| new UserHandle(mUserId)); |
| |
| } catch (NameNotFoundException e) { |
| e.printStackTrace(); |
| // This should never happen, but it's better to have no widgets than to crash. |
| userContext = context; |
| } |
| |
| mAppWidgetHost = new AppWidgetHost(userContext, APPWIDGET_HOST_ID, mOnClickHandler, |
| Looper.myLooper()); |
| |
| mAppWidgetManager = AppWidgetManager.getInstance(userContext); |
| |
| mViewStateManager = new KeyguardViewStateManager(this); |
| |
| mUserSetupCompleted = Settings.Secure.getIntForUser(mContext.getContentResolver(), |
| Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; |
| |
| // Ensure we have the current state *before* we call showAppropriateWidgetPage() |
| getInitialTransportState(); |
| |
| if (mSafeModeEnabled) { |
| Log.v(TAG, "Keyguard widgets disabled by safe mode"); |
| } |
| if ((mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0) { |
| Log.v(TAG, "Keyguard widgets disabled by DPM"); |
| } |
| if ((mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0) { |
| Log.v(TAG, "Keyguard secure camera disabled by DPM"); |
| } |
| } |
| |
| private void getInitialTransportState() { |
| DisplayClientState dcs = KeyguardUpdateMonitor.getInstance(mContext) |
| .getCachedDisplayClientState(); |
| mTransportState = (dcs.clearing ? TRANSPORT_GONE : |
| (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); |
| |
| if (DEBUGXPORT) Log.v(TAG, "Initial transport state: " |
| + mTransportState + ", pbstate=" + dcs.playbackState); |
| } |
| |
| private void cleanupAppWidgetIds() { |
| if (mSafeModeEnabled || widgetsDisabled()) return; |
| |
| // Clean up appWidgetIds that are bound to lockscreen, but not actually used |
| // This is only to clean up after another bug: we used to not call |
| // deleteAppWidgetId when a user manually deleted a widget in keyguard. This code |
| // shouldn't have to run more than once per user. AppWidgetProviders rely on callbacks |
| // that are triggered by deleteAppWidgetId, which is why we're doing this |
| int[] appWidgetIdsInKeyguardSettings = mLockPatternUtils.getAppWidgets(); |
| int[] appWidgetIdsBoundToHost = mAppWidgetHost.getAppWidgetIds(); |
| for (int i = 0; i < appWidgetIdsBoundToHost.length; i++) { |
| int appWidgetId = appWidgetIdsBoundToHost[i]; |
| if (!contains(appWidgetIdsInKeyguardSettings, appWidgetId)) { |
| Log.d(TAG, "Found a appWidgetId that's not being used by keyguard, deleting id " |
| + appWidgetId); |
| mAppWidgetHost.deleteAppWidgetId(appWidgetId); |
| } |
| } |
| } |
| |
| private static boolean contains(int[] array, int target) { |
| for (int value : array) { |
| if (value == target) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private KeyguardUpdateMonitorCallback mUpdateMonitorCallbacks = |
| new KeyguardUpdateMonitorCallback() { |
| @Override |
| public void onBootCompleted() { |
| if (mPostBootCompletedRunnable != null) { |
| mPostBootCompletedRunnable.run(); |
| mPostBootCompletedRunnable = null; |
| } |
| } |
| @Override |
| public void onUserSwitchComplete(int userId) { |
| if (mKeyguardMultiUserSelectorView != null) { |
| mKeyguardMultiUserSelectorView.finalizeActiveUserView(true); |
| } |
| } |
| @Override |
| void onMusicClientIdChanged( |
| int clientGeneration, boolean clearing, android.app.PendingIntent intent) { |
| // Set transport state to invisible until we know music is playing (below) |
| if (DEBUGXPORT && (mClientGeneration != clientGeneration || clearing)) { |
| Log.v(TAG, (clearing ? "hide" : "show") + " transport, gen:" + clientGeneration); |
| } |
| mClientGeneration = clientGeneration; |
| final int newState = (clearing ? TRANSPORT_GONE |
| : (mTransportState == TRANSPORT_VISIBLE ? |
| TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); |
| if (newState != mTransportState) { |
| mTransportState = newState; |
| if (DEBUGXPORT) Log.v(TAG, "update widget: transport state changed"); |
| KeyguardHostView.this.post(mSwitchPageRunnable); |
| } |
| } |
| @Override |
| public void onMusicPlaybackStateChanged(int playbackState, long eventTime) { |
| if (DEBUGXPORT) Log.v(TAG, "music state changed: " + playbackState); |
| if (mTransportState != TRANSPORT_GONE) { |
| final int newState = (isMusicPlaying(playbackState) ? |
| TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE); |
| if (newState != mTransportState) { |
| mTransportState = newState; |
| if (DEBUGXPORT) Log.v(TAG, "update widget: play state changed"); |
| KeyguardHostView.this.post(mSwitchPageRunnable); |
| } |
| } |
| } |
| @Override |
| public void onNfcUnlock() { |
| if (NfcUnlock.getPropertyEnabled()) { |
| dismiss(true); |
| } |
| } |
| }; |
| |
| private static final boolean isMusicPlaying(int playbackState) { |
| // This should agree with the list in AudioService.isPlaystateActive() |
| switch (playbackState) { |
| case RemoteControlClient.PLAYSTATE_PLAYING: |
| case RemoteControlClient.PLAYSTATE_BUFFERING: |
| case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: |
| case RemoteControlClient.PLAYSTATE_REWINDING: |
| case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: |
| case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private SlidingChallengeLayout mSlidingChallengeLayout; |
| private MultiPaneChallengeLayout mMultiPaneChallengeLayout; |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| boolean result = super.onTouchEvent(ev); |
| mTempRect.set(0, 0, 0, 0); |
| offsetRectIntoDescendantCoords(getSecurityContainer(), mTempRect); |
| ev.offsetLocation(mTempRect.left, mTempRect.top); |
| result = getSecurityContainer().dispatchTouchEvent(ev) || result; |
| ev.offsetLocation(-mTempRect.left, -mTempRect.top); |
| return result; |
| } |
| |
| private int getWidgetPosition(int id) { |
| final KeyguardWidgetPager appWidgetContainer = mAppWidgetContainer; |
| final int children = appWidgetContainer.getChildCount(); |
| for (int i = 0; i < children; i++) { |
| final View content = appWidgetContainer.getWidgetPageAt(i).getContent(); |
| if (content != null && content.getId() == id) { |
| return i; |
| } else if (content == null) { |
| // Attempt to track down bug #8886916 |
| Log.w(TAG, "*** Null content at " + "i=" + i + ",id=" + id + ",N=" + children); |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| // Grab instances of and make any necessary changes to the main layouts. Create |
| // view state manager and wire up necessary listeners / callbacks. |
| View deleteDropTarget = findViewById(R.id.keyguard_widget_pager_delete_target); |
| mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container); |
| mAppWidgetContainer.setVisibility(VISIBLE); |
| mAppWidgetContainer.setCallbacks(mWidgetCallbacks); |
| mAppWidgetContainer.setDeleteDropTarget(deleteDropTarget); |
| mAppWidgetContainer.setMinScale(0.5f); |
| |
| mSlidingChallengeLayout = (SlidingChallengeLayout) findViewById(R.id.sliding_layout); |
| if (mSlidingChallengeLayout != null) { |
| mSlidingChallengeLayout.setOnChallengeScrolledListener(mViewStateManager); |
| } |
| mAppWidgetContainer.setViewStateManager(mViewStateManager); |
| mAppWidgetContainer.setLockPatternUtils(mLockPatternUtils); |
| |
| mMultiPaneChallengeLayout = |
| (MultiPaneChallengeLayout) findViewById(R.id.multi_pane_challenge); |
| ChallengeLayout challenge = mSlidingChallengeLayout != null ? mSlidingChallengeLayout : |
| mMultiPaneChallengeLayout; |
| challenge.setOnBouncerStateChangedListener(mViewStateManager); |
| mAppWidgetContainer.setBouncerAnimationDuration(challenge.getBouncerAnimationDuration()); |
| mViewStateManager.setPagedView(mAppWidgetContainer); |
| mViewStateManager.setChallengeLayout(challenge); |
| |
| mViewStateManager.setSecurityViewContainer(getSecurityContainer()); |
| |
| if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { |
| updateAndAddWidgets(); |
| } else { |
| // We can't add widgets until after boot completes because AppWidgetHost may try |
| // to contact the providers. Do it later. |
| mPostBootCompletedRunnable = new Runnable() { |
| @Override |
| public void run() { |
| updateAndAddWidgets(); |
| } |
| }; |
| } |
| |
| getSecurityContainer().updateSecurityViews(mViewStateManager.isBouncing()); |
| enableUserSelectorIfNecessary(); |
| } |
| |
| private void updateAndAddWidgets() { |
| cleanupAppWidgetIds(); |
| addDefaultWidgets(); |
| addWidgetsFromSettings(); |
| maybeEnableAddButton(); |
| checkAppWidgetConsistency(); |
| |
| // Don't let the user drag the challenge down if widgets are disabled. |
| if (mSlidingChallengeLayout != null) { |
| mSlidingChallengeLayout.setEnableChallengeDragging(!widgetsDisabled()); |
| } |
| |
| // Select the appropriate page |
| mSwitchPageRunnable.run(); |
| |
| // This needs to be called after the pages are all added. |
| mViewStateManager.showUsabilityHints(); |
| } |
| |
| private void maybeEnableAddButton() { |
| if (!shouldEnableAddWidget()) { |
| mAppWidgetContainer.setAddWidgetEnabled(false); |
| } |
| } |
| |
| private boolean shouldEnableAddWidget() { |
| return numWidgets() < MAX_WIDGETS && mUserSetupCompleted; |
| } |
| |
| @Override |
| protected boolean dismiss(boolean authenticated) { |
| // TODO Auto-generated method stub |
| boolean finished = super.dismiss(authenticated); |
| if (!finished) { |
| mViewStateManager.showBouncer(true); |
| |
| // Enter full screen mode if we're in SIM or Account screen |
| SecurityMode securityMode = getSecurityContainer().getSecurityMode(); |
| boolean isFullScreen = getResources().getBoolean(R.bool.kg_sim_puk_account_full_screen); |
| boolean isSimOrAccount = securityMode == SecurityMode.SimPin |
| || securityMode == SecurityMode.SimPuk |
| || securityMode == SecurityMode.Account; |
| mAppWidgetContainer.setVisibility( |
| isSimOrAccount && isFullScreen ? View.GONE : View.VISIBLE); |
| |
| // Don't show camera or search in navbar when SIM or Account screen is showing |
| setSystemUiVisibility(isSimOrAccount ? |
| (getSystemUiVisibility() | View.STATUS_BAR_DISABLE_SEARCH) |
| : (getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_SEARCH)); |
| |
| if (mSlidingChallengeLayout != null) { |
| mSlidingChallengeLayout.setChallengeInteractive(!isFullScreen); |
| } |
| } |
| return finished; |
| } |
| |
| private int getDisabledFeatures(DevicePolicyManager dpm) { |
| int disabledFeatures = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; |
| if (dpm != null) { |
| final int currentUser = mLockPatternUtils.getCurrentUser(); |
| disabledFeatures = dpm.getKeyguardDisabledFeatures(null, currentUser); |
| } |
| return disabledFeatures; |
| } |
| |
| private boolean widgetsDisabled() { |
| boolean disabledByLowRamDevice = ActivityManager.isLowRamDeviceStatic(); |
| boolean disabledByDpm = |
| (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0; |
| boolean disabledByUser = !mLockPatternUtils.getWidgetsEnabled(); |
| return disabledByLowRamDevice || disabledByDpm || disabledByUser; |
| } |
| |
| private boolean cameraDisabledByDpm() { |
| return mCameraDisabled |
| || (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0; |
| } |
| |
| void setLockPatternUtils(LockPatternUtils utils) { |
| mLockPatternUtils = utils; |
| getSecurityContainer().setLockPatternUtils(utils); |
| getSecurityContainer().updateSecurityViews(mViewStateManager.isBouncing()); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| mAppWidgetHost.startListening(); |
| KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallbacks); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| mAppWidgetHost.stopListening(); |
| KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallbacks); |
| } |
| |
| void addWidget(AppWidgetHostView view, int pageIndex) { |
| mAppWidgetContainer.addWidget(view, pageIndex); |
| } |
| |
| private KeyguardWidgetPager.Callbacks mWidgetCallbacks |
| = new KeyguardWidgetPager.Callbacks() { |
| @Override |
| public void userActivity() { |
| KeyguardHostView.this.userActivity(); |
| } |
| |
| @Override |
| public void onUserActivityTimeoutChanged() { |
| KeyguardHostView.this.onUserActivityTimeoutChanged(); |
| } |
| |
| @Override |
| public void onAddView(View v) { |
| if (!shouldEnableAddWidget()) { |
| mAppWidgetContainer.setAddWidgetEnabled(false); |
| } |
| } |
| |
| @Override |
| public void onRemoveView(View v, boolean deletePermanently) { |
| if (deletePermanently) { |
| final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); |
| if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && |
| appWidgetId != LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) { |
| mAppWidgetHost.deleteAppWidgetId(appWidgetId); |
| } |
| } |
| } |
| |
| @Override |
| public void onRemoveViewAnimationCompleted() { |
| if (shouldEnableAddWidget()) { |
| mAppWidgetContainer.setAddWidgetEnabled(true); |
| } |
| } |
| }; |
| |
| public void initializeSwitchingUserState(boolean switching) { |
| if (!switching && mKeyguardMultiUserSelectorView != null) { |
| mKeyguardMultiUserSelectorView.finalizeActiveUserView(false); |
| } |
| } |
| |
| public void userActivity() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(); |
| } |
| } |
| |
| public void onUserActivityTimeoutChanged() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.onUserActivityTimeoutChanged(); |
| } |
| } |
| |
| @Override |
| public long getUserActivityTimeout() { |
| // Currently only considering user activity timeouts needed by widgets. |
| // Could also take into account longer timeouts for certain security views. |
| if (mAppWidgetContainer != null) { |
| return mAppWidgetContainer.getUserActivityTimeout(); |
| } |
| return -1; |
| } |
| |
| private static class MyOnClickHandler extends OnClickHandler { |
| |
| // weak reference to the hostView to avoid keeping a live reference |
| // due to Binder GC linkages to AppWidgetHost. By the same token, |
| // this click handler should not keep references to any large |
| // objects. |
| WeakReference<KeyguardHostView> mKeyguardHostView; |
| |
| MyOnClickHandler(KeyguardHostView hostView) { |
| mKeyguardHostView = new WeakReference<KeyguardHostView>(hostView); |
| } |
| |
| @Override |
| public boolean onClickHandler(final View view, |
| final android.app.PendingIntent pendingIntent, |
| final Intent fillInIntent) { |
| KeyguardHostView hostView = mKeyguardHostView.get(); |
| if (hostView == null) { |
| return false; |
| } |
| if (pendingIntent.isActivity()) { |
| hostView.setOnDismissAction(new OnDismissAction() { |
| public boolean onDismiss() { |
| try { |
| // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? |
| Context context = view.getContext(); |
| ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, |
| 0, 0, |
| view.getMeasuredWidth(), view.getMeasuredHeight()); |
| context.startIntentSender( |
| pendingIntent.getIntentSender(), fillInIntent, |
| Intent.FLAG_ACTIVITY_NEW_TASK, |
| Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); |
| } catch (IntentSender.SendIntentException e) { |
| android.util.Log.e(TAG, "Cannot send pending intent: ", e); |
| } catch (Exception e) { |
| android.util.Log.e(TAG, "Cannot send pending intent due to " + |
| "unknown exception: ", e); |
| } |
| return false; |
| } |
| }); |
| |
| if (hostView.mViewStateManager.isChallengeShowing()) { |
| hostView.mViewStateManager.showBouncer(true); |
| } else { |
| hostView.dismiss(); |
| } |
| return true; |
| } else { |
| return super.onClickHandler(view, pendingIntent, fillInIntent); |
| } |
| }; |
| }; |
| |
| @Override |
| public void onScreenTurnedOn() { |
| super.onScreenTurnedOn(); |
| if (mViewStateManager != null) { |
| mViewStateManager.showUsabilityHints(); |
| } |
| } |
| |
| @Override |
| public void onScreenTurnedOff() { |
| super.onScreenTurnedOff(); |
| // We use mAppWidgetToShow to show a particular widget after you add it-- once the screen |
| // turns off we reset that behavior |
| clearAppWidgetToShow(); |
| if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) { |
| checkAppWidgetConsistency(); |
| } |
| CameraWidgetFrame cameraPage = findCameraPage(); |
| if (cameraPage != null) { |
| cameraPage.onScreenTurnedOff(); |
| } |
| } |
| |
| public void clearAppWidgetToShow() { |
| mAppWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; |
| } |
| |
| private boolean addWidget(int appId, int pageIndex, boolean updateDbIfFailed) { |
| AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId); |
| if (appWidgetInfo != null) { |
| AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo); |
| addWidget(view, pageIndex); |
| return true; |
| } else { |
| if (updateDbIfFailed) { |
| Log.w(TAG, "*** AppWidgetInfo for app widget id " + appId + " was null for user" |
| + mUserId + ", deleting"); |
| mAppWidgetHost.deleteAppWidgetId(appId); |
| mLockPatternUtils.removeAppWidget(appId); |
| } |
| return false; |
| } |
| } |
| |
| private final CameraWidgetFrame.Callbacks mCameraWidgetCallbacks = |
| new CameraWidgetFrame.Callbacks() { |
| @Override |
| public void onLaunchingCamera() { |
| setSliderHandleAlpha(0); |
| } |
| |
| @Override |
| public void onCameraLaunchedSuccessfully() { |
| if (mAppWidgetContainer.isCameraPage(mAppWidgetContainer.getCurrentPage())) { |
| mAppWidgetContainer.scrollLeft(); |
| } |
| setSliderHandleAlpha(1); |
| mShowSecurityWhenReturn = true; |
| } |
| |
| @Override |
| public void onCameraLaunchedUnsuccessfully() { |
| setSliderHandleAlpha(1); |
| } |
| |
| private void setSliderHandleAlpha(float alpha) { |
| SlidingChallengeLayout slider = |
| (SlidingChallengeLayout) findViewById(R.id.sliding_layout); |
| if (slider != null) { |
| slider.setHandleAlpha(alpha); |
| } |
| } |
| }; |
| |
| private int numWidgets() { |
| final int childCount = mAppWidgetContainer.getChildCount(); |
| int widgetCount = 0; |
| for (int i = 0; i < childCount; i++) { |
| if (mAppWidgetContainer.isWidgetPage(i)) { |
| widgetCount++; |
| } |
| } |
| return widgetCount; |
| } |
| |
| private void addDefaultWidgets() { |
| if (!mSafeModeEnabled && !widgetsDisabled()) { |
| LayoutInflater inflater = LayoutInflater.from(mContext); |
| View addWidget = inflater.inflate(R.layout.keyguard_add_widget, this, false); |
| mAppWidgetContainer.addWidget(addWidget, 0); |
| View addWidgetButton = addWidget.findViewById(R.id.keyguard_add_widget_view); |
| addWidgetButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| // Pass in an invalid widget id... the picker will allocate an ID for us |
| getActivityLauncher().launchWidgetPicker(AppWidgetManager.INVALID_APPWIDGET_ID); |
| } |
| }); |
| } |
| |
| // We currently disable cameras in safe mode because we support loading 3rd party |
| // cameras we can't trust. TODO: plumb safe mode into camera creation code and only |
| // inflate system-provided camera? |
| if (!mSafeModeEnabled && !cameraDisabledByDpm() && mUserSetupCompleted |
| && mContext.getResources().getBoolean(R.bool.kg_enable_camera_default_widget)) { |
| View cameraWidget = CameraWidgetFrame.create(mContext, mCameraWidgetCallbacks, |
| getActivityLauncher()); |
| if (cameraWidget != null) { |
| mAppWidgetContainer.addWidget(cameraWidget); |
| } |
| } |
| } |
| |
| /** |
| * Create KeyguardTransportControlView on demand. |
| * @return |
| */ |
| private KeyguardTransportControlView getOrCreateTransportControl() { |
| if (mTransportControl == null) { |
| LayoutInflater inflater = LayoutInflater.from(mContext); |
| mTransportControl = (KeyguardTransportControlView) |
| inflater.inflate(R.layout.keyguard_transport_control_view, this, false); |
| mTransportControl.setTransportControlCallback(new TransportControlCallback() { |
| public void userActivity() { |
| mViewMediatorCallback.userActivity(); |
| } |
| }); |
| } |
| return mTransportControl; |
| } |
| |
| private int getInsertPageIndex() { |
| View addWidget = mAppWidgetContainer.findViewById(R.id.keyguard_add_widget); |
| int insertionIndex = mAppWidgetContainer.indexOfChild(addWidget); |
| if (insertionIndex < 0) { |
| insertionIndex = 0; // no add widget page found |
| } else { |
| insertionIndex++; // place after add widget |
| } |
| return insertionIndex; |
| } |
| |
| private void addDefaultStatusWidget(int index) { |
| LayoutInflater inflater = LayoutInflater.from(mContext); |
| View statusWidget = inflater.inflate(R.layout.keyguard_status_view, null, true); |
| mAppWidgetContainer.addWidget(statusWidget, index); |
| } |
| |
| private void addWidgetsFromSettings() { |
| if (mSafeModeEnabled || widgetsDisabled()) { |
| addDefaultStatusWidget(0); |
| return; |
| } |
| |
| int insertionIndex = getInsertPageIndex(); |
| |
| // Add user-selected widget |
| final int[] widgets = mLockPatternUtils.getAppWidgets(); |
| |
| if (widgets == null) { |
| Log.d(TAG, "Problem reading widgets"); |
| } else { |
| for (int i = widgets.length -1; i >= 0; i--) { |
| if (widgets[i] == LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) { |
| addDefaultStatusWidget(insertionIndex); |
| } else { |
| // We add the widgets from left to right, starting after the first page after |
| // the add page. We count down, since the order will be persisted from right |
| // to left, starting after camera. |
| addWidget(widgets[i], insertionIndex, true); |
| } |
| } |
| } |
| } |
| |
| private int allocateIdForDefaultAppWidget() { |
| int appWidgetId; |
| Resources res = getContext().getResources(); |
| ComponentName defaultAppWidget = new ComponentName( |
| res.getString(R.string.widget_default_package_name), |
| res.getString(R.string.widget_default_class_name)); |
| |
| // Note: we don't support configuring the widget |
| appWidgetId = mAppWidgetHost.allocateAppWidgetId(); |
| |
| try { |
| mAppWidgetManager.bindAppWidgetId(appWidgetId, defaultAppWidget); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Error when trying to bind default AppWidget: " + e); |
| mAppWidgetHost.deleteAppWidgetId(appWidgetId); |
| appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; |
| } |
| return appWidgetId; |
| } |
| |
| public void checkAppWidgetConsistency() { |
| final int childCount = mAppWidgetContainer.getChildCount(); |
| boolean widgetPageExists = false; |
| for (int i = 0; i < childCount; i++) { |
| if (mAppWidgetContainer.isWidgetPage(i)) { |
| widgetPageExists = true; |
| break; |
| } |
| } |
| if (!widgetPageExists) { |
| final int insertPageIndex = getInsertPageIndex(); |
| |
| final boolean userAddedWidgetsEnabled = !widgetsDisabled(); |
| |
| boolean addedDefaultAppWidget = false; |
| |
| if (!mSafeModeEnabled) { |
| if (userAddedWidgetsEnabled) { |
| int appWidgetId = allocateIdForDefaultAppWidget(); |
| if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { |
| addedDefaultAppWidget = addWidget(appWidgetId, insertPageIndex, true); |
| } |
| } else { |
| // note: even if widgetsDisabledByDpm() returns true, we still bind/create |
| // the default appwidget if possible |
| int appWidgetId = mLockPatternUtils.getFallbackAppWidgetId(); |
| if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { |
| appWidgetId = allocateIdForDefaultAppWidget(); |
| if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { |
| mLockPatternUtils.writeFallbackAppWidgetId(appWidgetId); |
| } |
| } |
| if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { |
| addedDefaultAppWidget = addWidget(appWidgetId, insertPageIndex, false); |
| if (!addedDefaultAppWidget) { |
| mAppWidgetHost.deleteAppWidgetId(appWidgetId); |
| mLockPatternUtils.writeFallbackAppWidgetId( |
| AppWidgetManager.INVALID_APPWIDGET_ID); |
| } |
| } |
| } |
| } |
| |
| // Use the built-in status/clock view if we can't inflate the default widget |
| if (!addedDefaultAppWidget) { |
| addDefaultStatusWidget(insertPageIndex); |
| } |
| |
| // trigger DB updates only if user-added widgets are enabled |
| if (!mSafeModeEnabled && userAddedWidgetsEnabled) { |
| mAppWidgetContainer.onAddView( |
| mAppWidgetContainer.getChildAt(insertPageIndex), insertPageIndex); |
| } |
| } |
| } |
| |
| private final Runnable mSwitchPageRunnable = new Runnable() { |
| @Override |
| public void run() { |
| showAppropriateWidgetPage(); |
| } |
| }; |
| |
| static class SavedState extends BaseSavedState { |
| int transportState; |
| int appWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; |
| Rect insets = new Rect(); |
| |
| SavedState(Parcelable superState) { |
| super(superState); |
| } |
| |
| private SavedState(Parcel in) { |
| super(in); |
| this.transportState = in.readInt(); |
| this.appWidgetToShow = in.readInt(); |
| this.insets = in.readParcelable(null); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel out, int flags) { |
| super.writeToParcel(out, flags); |
| out.writeInt(this.transportState); |
| out.writeInt(this.appWidgetToShow); |
| out.writeParcelable(insets, 0); |
| } |
| |
| public static final Parcelable.Creator<SavedState> CREATOR |
| = new Parcelable.Creator<SavedState>() { |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in); |
| } |
| |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| |
| @Override |
| public Parcelable onSaveInstanceState() { |
| if (DEBUG) Log.d(TAG, "onSaveInstanceState, tstate=" + mTransportState); |
| Parcelable superState = super.onSaveInstanceState(); |
| SavedState ss = new SavedState(superState); |
| // If the transport is showing, force it to show it on restore. |
| final boolean showing = mTransportControl != null |
| && mAppWidgetContainer.getWidgetPageIndex(mTransportControl) >= 0; |
| ss.transportState = showing ? TRANSPORT_VISIBLE : mTransportState; |
| ss.appWidgetToShow = mAppWidgetToShow; |
| ss.insets.set(mInsets); |
| return ss; |
| } |
| |
| @Override |
| public void onRestoreInstanceState(Parcelable state) { |
| if (!(state instanceof SavedState)) { |
| super.onRestoreInstanceState(state); |
| return; |
| } |
| SavedState ss = (SavedState) state; |
| super.onRestoreInstanceState(ss.getSuperState()); |
| mTransportState = (ss.transportState); |
| mAppWidgetToShow = ss.appWidgetToShow; |
| setInsets(ss.insets); |
| if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState); |
| mSwitchPageRunnable.run(); |
| } |
| |
| @Override |
| protected boolean fitSystemWindows(Rect insets) { |
| setInsets(insets); |
| return true; |
| } |
| |
| private void setInsets(Rect insets) { |
| mInsets.set(insets); |
| if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setInsets(mInsets); |
| if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setInsets(mInsets); |
| |
| final CameraWidgetFrame cameraWidget = findCameraPage(); |
| if (cameraWidget != null) cameraWidget.setInsets(mInsets); |
| } |
| |
| @Override |
| public void onWindowFocusChanged(boolean hasWindowFocus) { |
| super.onWindowFocusChanged(hasWindowFocus); |
| if (DEBUG) Log.d(TAG, "Window is " + (hasWindowFocus ? "focused" : "unfocused")); |
| if (hasWindowFocus && mShowSecurityWhenReturn) { |
| SlidingChallengeLayout slider = |
| (SlidingChallengeLayout) findViewById(R.id.sliding_layout); |
| if (slider != null) { |
| slider.setHandleAlpha(1); |
| slider.showChallenge(true); |
| } |
| mShowSecurityWhenReturn = false; |
| } |
| } |
| |
| private void showAppropriateWidgetPage() { |
| final int state = mTransportState; |
| final boolean transportAdded = ensureTransportPresentOrRemoved(state); |
| final int pageToShow = getAppropriateWidgetPage(state); |
| if (!transportAdded) { |
| mAppWidgetContainer.setCurrentPage(pageToShow); |
| } else if (state == TRANSPORT_VISIBLE) { |
| // If the transport was just added, we need to wait for layout to happen before |
| // we can set the current page. |
| post(new Runnable() { |
| @Override |
| public void run() { |
| mAppWidgetContainer.setCurrentPage(pageToShow); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Examines the current state and adds the transport to the widget pager when the state changes. |
| * |
| * Showing the initial transport and keeping it around is a bit tricky because the signals |
| * coming from music players aren't always clear. Here's how the states are handled: |
| * |
| * {@link TRANSPORT_GONE} means we have no reason to show the transport - remove it if present. |
| * |
| * {@link TRANSPORT_INVISIBLE} means we have potential to show the transport because a music |
| * player is registered but not currently playing music (or we don't know the state yet). The |
| * code adds it conditionally on play state. |
| * |
| * {@link #TRANSPORT_VISIBLE} means a music player is active and transport should be showing. |
| * |
| * Once the transport is showing, we always show it until keyguard is dismissed. This state is |
| * maintained by onSave/RestoreInstanceState(). This state is cleared in |
| * {@link KeyguardViewManager#hide} when keyguard is dismissed, which causes the transport to be |
| * gone when keyguard is restarted until we get an update with the current state. |
| * |
| * @param state |
| */ |
| private boolean ensureTransportPresentOrRemoved(int state) { |
| final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1; |
| final boolean visible = state == TRANSPORT_VISIBLE; |
| final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state); |
| if (!showing && (visible || shouldBeVisible)) { |
| // insert to left of camera if it exists, otherwise after right-most widget |
| int lastWidget = mAppWidgetContainer.getChildCount() - 1; |
| int position = 0; // handle no widget case |
| if (lastWidget >= 0) { |
| position = mAppWidgetContainer.isCameraPage(lastWidget) ? |
| lastWidget : lastWidget + 1; |
| } |
| if (DEBUGXPORT) Log.v(TAG, "add transport at " + position); |
| mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position); |
| return true; |
| } else if (showing && state == TRANSPORT_GONE) { |
| if (DEBUGXPORT) Log.v(TAG, "remove transport"); |
| mAppWidgetContainer.removeWidget(getOrCreateTransportControl()); |
| mTransportControl = null; |
| KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null); |
| } |
| return false; |
| } |
| |
| private CameraWidgetFrame findCameraPage() { |
| for (int i = mAppWidgetContainer.getChildCount() - 1; i >= 0; i--) { |
| if (mAppWidgetContainer.isCameraPage(i)) { |
| return (CameraWidgetFrame) mAppWidgetContainer.getChildAt(i); |
| } |
| } |
| return null; |
| } |
| |
| boolean isMusicPage(int pageIndex) { |
| return pageIndex >= 0 && pageIndex == getWidgetPosition(R.id.keyguard_transport_control); |
| } |
| |
| private int getAppropriateWidgetPage(int musicTransportState) { |
| // assumes at least one widget (besides camera + add) |
| if (mAppWidgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { |
| final int childCount = mAppWidgetContainer.getChildCount(); |
| for (int i = 0; i < childCount; i++) { |
| if (mAppWidgetContainer.getWidgetPageAt(i).getContentAppWidgetId() |
| == mAppWidgetToShow) { |
| return i; |
| } |
| } |
| mAppWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID; |
| } |
| // if music playing, show transport |
| if (musicTransportState == TRANSPORT_VISIBLE) { |
| if (DEBUG) Log.d(TAG, "Music playing, show transport"); |
| return mAppWidgetContainer.getWidgetPageIndex(getOrCreateTransportControl()); |
| } |
| |
| // else show the right-most widget (except for camera) |
| int rightMost = mAppWidgetContainer.getChildCount() - 1; |
| if (mAppWidgetContainer.isCameraPage(rightMost)) { |
| rightMost--; |
| } |
| if (DEBUG) Log.d(TAG, "Show right-most page " + rightMost); |
| return rightMost; |
| } |
| |
| private void enableUserSelectorIfNecessary() { |
| if (!UserManager.supportsMultipleUsers()) { |
| return; // device doesn't support multi-user mode |
| } |
| final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| if (um == null) { |
| Throwable t = new Throwable(); |
| t.fillInStackTrace(); |
| Log.e(TAG, "user service is null.", t); |
| return; |
| } |
| |
| // if there are multiple users, we need to enable to multi-user switcher |
| final List<UserInfo> users = um.getUsers(true); |
| if (users == null) { |
| Throwable t = new Throwable(); |
| t.fillInStackTrace(); |
| Log.e(TAG, "list of users is null.", t); |
| return; |
| } |
| |
| final View multiUserView = findViewById(R.id.keyguard_user_selector); |
| if (multiUserView == null) { |
| Throwable t = new Throwable(); |
| t.fillInStackTrace(); |
| Log.e(TAG, "can't find user_selector in layout.", t); |
| return; |
| } |
| |
| if (users.size() > 1) { |
| if (multiUserView instanceof KeyguardMultiUserSelectorView) { |
| mKeyguardMultiUserSelectorView = (KeyguardMultiUserSelectorView) multiUserView; |
| mKeyguardMultiUserSelectorView.setVisibility(View.VISIBLE); |
| mKeyguardMultiUserSelectorView.addUsers(users); |
| UserSwitcherCallback callback = new UserSwitcherCallback() { |
| @Override |
| public void hideSecurityView(int duration) { |
| getSecurityContainer().animate().alpha(0).setDuration(duration); |
| } |
| |
| @Override |
| public void showSecurityView() { |
| getSecurityContainer().setAlpha(1.0f); |
| } |
| |
| @Override |
| public void showUnlockHint() { |
| if (getSecurityContainer() != null) { |
| getSecurityContainer().showUsabilityHint(); |
| } |
| } |
| |
| @Override |
| public void userActivity() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(); |
| } |
| } |
| }; |
| mKeyguardMultiUserSelectorView.setCallback(callback); |
| } else { |
| Throwable t = new Throwable(); |
| t.fillInStackTrace(); |
| if (multiUserView == null) { |
| Log.e(TAG, "could not find the user_selector.", t); |
| } else { |
| Log.e(TAG, "user_selector is the wrong type.", t); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void cleanUp() { |
| // Make sure we let go of all widgets and their package contexts promptly. If we don't do |
| // this, and the associated application is uninstalled, it can cause a soft reboot. |
| int count = mAppWidgetContainer.getChildCount(); |
| for (int i = 0; i < count; i++) { |
| KeyguardWidgetFrame frame = mAppWidgetContainer.getWidgetPageAt(i); |
| frame.removeAllViews(); |
| } |
| } |
| |
| public void goToWidget(int appWidgetId) { |
| mAppWidgetToShow = appWidgetId; |
| mSwitchPageRunnable.run(); |
| } |
| |
| @Override |
| protected void showBouncer(boolean show) { |
| super.showBouncer(show); |
| mViewStateManager.showBouncer(show); |
| } |
| |
| public void dispatch(MotionEvent event) { |
| mAppWidgetContainer.handleExternalCameraEvent(event); |
| } |
| |
| } |