Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 860a2c03 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

3/N Use KeyguardSecurityContainerController in KHVC.

Use the KeyguardSecurityContainerController in the
KeyguardHostViewController instead of the view directly.

This actually cleans up KeyguardHostView quite a bit,
with all lot of its business logic moved over to its
view.

The KeyguardSecurityContainerController doesn't do much
except to proxy through calls to its view for now.

Bug: 166448040
Test: atest SystemUITests && manual
Change-Id: I96a27b673c4579983bb07b7fb7ef321a022f0f65
parent f6032e30
Loading
Loading
Loading
Loading
+1 −358
Original line number Diff line number Diff line
@@ -16,30 +16,11 @@

package com.android.keyguard;

import android.app.ActivityManager;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.media.AudioManager;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.FrameLayout;

import androidx.annotation.VisibleForTesting;

import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;

import java.io.File;

/**
 * Base class for keyguard view.  {@link #reset} is where you should
 * reset the state of your view.  Use the {@link KeyguardViewCallback} via
@@ -49,25 +30,10 @@ import java.io.File;
 * Handles intercepting of media keys that still work when the keyguard is
 * showing.
 */
public class KeyguardHostView extends FrameLayout implements SecurityCallback {
public class KeyguardHostView extends FrameLayout {

    private AudioManager mAudioManager;
    private TelephonyManager mTelephonyManager = null;
    protected ViewMediatorCallback mViewMediatorCallback;
    protected LockPatternUtils mLockPatternUtils;
    private OnDismissAction mDismissAction;
    private Runnable mCancelAction;


    // Whether the volume keys should be handled by keyguard. If true, then
    // they will be handled here for specific media types such as music, otherwise
    // the audio service will bring up the volume dialog.
    private static final boolean KEYGUARD_MANAGES_VOLUME = false;
    public static final boolean DEBUG = KeyguardConstants.DEBUG;
    private static final String TAG = "KeyguardViewBase";

    @VisibleForTesting
    protected KeyguardSecurityContainer mSecurityContainer;

    public KeyguardHostView(Context context) {
        this(context, null);
@@ -85,330 +51,7 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
        }
    }

    /**
     * Sets an action to run when keyguard finishes.
     *
     * @param action
     */
    public void setOnDismissAction(OnDismissAction action, Runnable cancelAction) {
        if (mCancelAction != null) {
            mCancelAction.run();
            mCancelAction = null;
        }
        mDismissAction = action;
        mCancelAction = cancelAction;
    }

    public boolean hasDismissActions() {
        return mDismissAction != null || mCancelAction != null;
    }

    public void cancelDismissAction() {
        setOnDismissAction(null, null);
    }

    @Override
    protected void onFinishInflate() {
        mSecurityContainer =
                findViewById(R.id.keyguard_security_container);
        mLockPatternUtils = new LockPatternUtils(mContext);
        mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
        mSecurityContainer.setSecurityCallback(this);
        mSecurityContainer.showPrimarySecurityScreen(false);
    }

    /**
     * Called when the view needs to be shown.
     */
    public void showPrimarySecurityScreen() {
        if (DEBUG) Log.d(TAG, "show()");
        mSecurityContainer.showPrimarySecurityScreen(false);
    }

    public KeyguardSecurityView getCurrentSecurityView() {
        return mSecurityContainer != null ? mSecurityContainer.getCurrentSecurityView() : null;
    }

    /**
     * Show a string explaining why the security view needs to be solved.
     *
     * @param reason a flag indicating which string should be shown, see
     *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
     *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
     *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
     *               {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
     */
    public void showPromptReason(int reason) {
        mSecurityContainer.showPromptReason(reason);
    }

    public void showMessage(CharSequence message, ColorStateList colorState) {
        mSecurityContainer.showMessage(message, colorState);
    }

    public void showErrorMessage(CharSequence message) {
        showMessage(message, Utils.getColorError(mContext));
    }

    /**
     * Dismisses the keyguard by going to the next screen or making it gone.
     * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
     * @return True if the keyguard is done.
     */
    public boolean dismiss(int targetUserId) {
        return dismiss(false, targetUserId, false);
    }

    public boolean handleBackKey() {
        if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
            mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser());
            return true;
        }
        return false;
    }

    protected KeyguardSecurityContainer getSecurityContainer() {
        return mSecurityContainer;
    }

    @Override
    public boolean dismiss(boolean authenticated, int targetUserId,
            boolean bypassSecondaryLockScreen) {
        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId,
                bypassSecondaryLockScreen);
    }

    /**
     * Authentication has happened and it's time to dismiss keyguard. This function
     * should clean up and inform KeyguardViewMediator.
     *
     * @param strongAuth whether the user has authenticated with strong authentication like
     *                   pattern, password or PIN but not by trust agents or fingerprint
     * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
     */
    @Override
    public void finish(boolean strongAuth, int targetUserId) {
        // If there's a pending runnable because the user interacted with a widget
        // and we're leaving keyguard, then run it.
        boolean deferKeyguardDone = false;
        if (mDismissAction != null) {
            deferKeyguardDone = mDismissAction.onDismiss();
            mDismissAction = null;
            mCancelAction = null;
        }
        if (mViewMediatorCallback != null) {
            if (deferKeyguardDone) {
                mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
            } else {
                mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
            }
        }
    }

    @Override
    public void reset() {
        mViewMediatorCallback.resetKeyguard();
    }

    @Override
    public void onCancelClicked() {
        mViewMediatorCallback.onCancelClicked();
    }

    public void resetSecurityContainer() {
        mSecurityContainer.reset();
    }

    @Override
    public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
        if (mViewMediatorCallback != null) {
            mViewMediatorCallback.setNeedsInput(needsInput);
        }
    }

    public CharSequence getAccessibilityTitleForCurrentMode() {
        return mSecurityContainer.getTitle();
    }

    public void userActivity() {
        if (mViewMediatorCallback != null) {
            mViewMediatorCallback.userActivity();
        }
    }

    /**
     * Called when the Keyguard is not actively shown anymore on the screen.
     */
    public void onPause() {
        if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
                Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
        mSecurityContainer.showPrimarySecurityScreen(true);
        mSecurityContainer.onPause();
        clearFocus();
    }

    /**
     * Called when the Keyguard is actively shown on the screen.
     */
    public void onResume() {
        if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
        mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
        requestFocus();
    }

    /**
     * Starts the animation when the Keyguard gets shown.
     */
    public void startAppearAnimation() {
        mSecurityContainer.startAppearAnimation();
    }

    public void startDisappearAnimation(Runnable finishRunnable) {
        if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
            finishRunnable.run();
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (interceptMediaKey(event)) {
            return true;
        }
        return super.dispatchKeyEvent(event);
    }

    /**
     * Allows the media keys to work when the keyguard is showing.
     * The media keys should be of no interest to the actual keyguard view(s),
     * so intercepting them here should not be of any harm.
     * @param event The key event
     * @return whether the event was consumed as a media key.
     */
    public boolean interceptMediaKey(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_MEDIA_PLAY:
                case KeyEvent.KEYCODE_MEDIA_PAUSE:
                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
                     * in-call to avoid music playback */
                    if (mTelephonyManager == null) {
                        mTelephonyManager = (TelephonyManager) getContext().getSystemService(
                                Context.TELEPHONY_SERVICE);
                    }
                    if (mTelephonyManager != null &&
                            mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
                        return true;  // suppress key event
                    }
                case KeyEvent.KEYCODE_MUTE:
                case KeyEvent.KEYCODE_HEADSETHOOK:
                case KeyEvent.KEYCODE_MEDIA_STOP:
                case KeyEvent.KEYCODE_MEDIA_NEXT:
                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
                case KeyEvent.KEYCODE_MEDIA_REWIND:
                case KeyEvent.KEYCODE_MEDIA_RECORD:
                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
                    handleMediaKeyEvent(event);
                    return true;
                }

                case KeyEvent.KEYCODE_VOLUME_UP:
                case KeyEvent.KEYCODE_VOLUME_DOWN:
                case KeyEvent.KEYCODE_VOLUME_MUTE: {
                    if (KEYGUARD_MANAGES_VOLUME) {
                        synchronized (this) {
                            if (mAudioManager == null) {
                                mAudioManager = (AudioManager) getContext().getSystemService(
                                        Context.AUDIO_SERVICE);
                            }
                        }
                        // Volume buttons should only function for music (local or remote).
                        // TODO: Actually handle MUTE.
                        mAudioManager.adjustSuggestedStreamVolume(
                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
                                        ? AudioManager.ADJUST_RAISE
                                        : AudioManager.ADJUST_LOWER /* direction */,
                                AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
                        // Don't execute default volume behavior
                        return true;
                    } else {
                        return false;
                    }
                }
            }
        } else if (event.getAction() == KeyEvent.ACTION_UP) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_MUTE:
                case KeyEvent.KEYCODE_HEADSETHOOK:
                case KeyEvent.KEYCODE_MEDIA_PLAY:
                case KeyEvent.KEYCODE_MEDIA_PAUSE:
                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_RECORD:
                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
                    handleMediaKeyEvent(event);
                    return true;
                }
            }
        }
        return false;
    }

    private void handleMediaKeyEvent(KeyEvent keyEvent) {
        synchronized (this) {
            if (mAudioManager == null) {
                mAudioManager = (AudioManager) getContext().getSystemService(
                        Context.AUDIO_SERVICE);
            }
        }
        mAudioManager.dispatchMediaKeyEvent(keyEvent);
    }

    /**
     * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
     * some cases where we wish to disable it, notably when the menu button placement or technology
     * is prone to false positives.
     *
     * @return true if the menu key should be enabled
     */
    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
    public boolean shouldEnableMenuKey() {
        final Resources res = getResources();
        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
        return !configDisabled || isTestHarness || fileOverride;
    }

    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
        mViewMediatorCallback = viewMediatorCallback;
        // Update ViewMediator with the current input method requirements
        mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
    }

    public void setLockPatternUtils(LockPatternUtils utils) {
        mLockPatternUtils = utils;
        mSecurityContainer.setLockPatternUtils(utils);
    }

    public SecurityMode getSecurityMode() {
        return mSecurityContainer.getSecurityMode();
    }

    public SecurityMode getCurrentSecurityMode() {
        return mSecurityContainer.getCurrentSecurityMode();
    }

    /**
     * When bouncer was visible and is starting to become hidden.
     */
    public void onStartingToHide() {
        mSecurityContainer.onStartingToHide();
    }
}
+264 −34

File changed.

Preview size limit exceeded, changes collapsed.

+71 −2
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package com.android.keyguard;

import android.content.res.ColorStateList;

import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.util.ViewController;

import javax.inject.Inject;
@@ -23,27 +28,91 @@ import javax.inject.Inject;
/** Controller for {@link KeyguardSecurityContainer} */
public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> {

    private final LockPatternUtils mLockPatternUtils;
    private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory;

    @Inject
    KeyguardSecurityContainerController(KeyguardSecurityContainer view,
            LockPatternUtils lockPatternUtils,
            KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) {
        super(view);
        mLockPatternUtils = lockPatternUtils;
        view.setLockPatternUtils(mLockPatternUtils);
        mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
    }

    @Override
    protected void onViewAttached() {

    }

    @Override
    protected void onViewDetached() {

    }

    /** */
    public void onPause() {
        mView.onPause();
    }

    public void showPrimarySecurityScreen(boolean turningOff) {
        mView.showPrimarySecurityScreen(turningOff);
    }

    public void showPromptReason(int reason) {
        mView.showPromptReason(reason);
    }

    public void showMessage(CharSequence message, ColorStateList colorState) {
        mView.showMessage(message, colorState);
    }

    public SecurityMode getCurrentSecuritySelection() {
        return mView.getCurrentSecuritySelection();
    }

    public void dismiss(boolean authenticated, int targetUserId) {
        mView.dismiss(authenticated, targetUserId);
    }

    public void reset() {
        mView.reset();
    }

    public CharSequence getTitle() {
        return mView.getTitle();
    }

    public void onResume(int screenOn) {
        mView.onResume(screenOn);
    }

    public void startAppearAnimation() {
        mView.startAppearAnimation();
    }

    public boolean startDisappearAnimation(Runnable onFinishRunnable) {
        return mView.startDisappearAnimation(onFinishRunnable);
    }

    public void onStartingToHide() {
        mView.onStartingToHide();
    }

    public void setSecurityCallback(SecurityCallback securityCallback) {
        mView.setSecurityCallback(securityCallback);
    }

    public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
            boolean bypassSecondaryLockScreen) {
        return mView.showNextSecurityScreenOrFinish(
                authenticated, targetUserId, bypassSecondaryLockScreen);
    }

    public boolean needsInput() {
        return mView.needsInput();
    }

    public SecurityMode getCurrentSecurityMode() {
        return mView.getCurrentSecurityMode();
    }
}
+24 −20
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
 * 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
 * limitations under the License.
 */

package com.android.keyguard;
@@ -19,11 +19,12 @@ package com.android.keyguard;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import android.media.AudioManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;

@@ -39,41 +40,44 @@ import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class KeyguardHostViewTest extends SysuiTestCase {
public class KeyguardHostViewControllerTest extends SysuiTestCase {

    @Mock
    private KeyguardSecurityContainer mSecurityContainer;
    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    @Mock
    private LockPatternUtils mLockPatternUtils;
    private KeyguardHostView mKeyguardHostView;
    @Mock
    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
    @Mock
    private AudioManager mAudioManager;
    @Mock
    private TelephonyManager mTelephonyManager;
    @Mock
    private ViewMediatorCallback mViewMediatorCallback;

    @Rule
    public MockitoRule mMockitoRule = MockitoJUnit.rule();

    private KeyguardHostView mKeyguardHostView;
    private KeyguardHostViewController mKeyguardHostViewController;

    @Before
    public void setup() {
        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
        mKeyguardHostView = new KeyguardHostView(getContext()) {
            @Override
            protected void onFinishInflate() {
                mSecurityContainer = KeyguardHostViewTest.this.mSecurityContainer;
                mLockPatternUtils = KeyguardHostViewTest.this.mLockPatternUtils;
            }
        };
        mKeyguardHostView.onFinishInflate();
        mKeyguardHostViewController = new KeyguardHostViewController(
                mKeyguardHostView, mKeyguardUpdateMonitor, mKeyguardSecurityContainerController,
                mAudioManager, mTelephonyManager, mViewMediatorCallback);
    }

    @Test
    public void testHasDismissActions() {
        Assert.assertFalse("Action not set yet", mKeyguardHostView.hasDismissActions());
        mKeyguardHostView.setOnDismissAction(mock(OnDismissAction.class),
        Assert.assertFalse("Action not set yet", mKeyguardHostViewController.hasDismissActions());
        mKeyguardHostViewController.setOnDismissAction(mock(OnDismissAction.class),
                null /* cancelAction */);
        Assert.assertTrue("Action should exist", mKeyguardHostView.hasDismissActions());
        Assert.assertTrue("Action should exist", mKeyguardHostViewController.hasDismissActions());
    }

    @Test
    public void testOnStartingToHide() {
        mKeyguardHostView.onStartingToHide();
        verify(mSecurityContainer).onStartingToHide();
        mKeyguardHostViewController.onStartingToHide();
        verify(mKeyguardSecurityContainerController).onStartingToHide();
    }
}