Merge "Check condition presence before updating callbacks." into tm-qpr-dev
diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java
index d730129..e448706 100644
--- a/core/java/android/debug/AdbManagerInternal.java
+++ b/core/java/android/debug/AdbManagerInternal.java
@@ -55,6 +55,12 @@
public abstract File getAdbTempKeysFile();
/**
+ * Notify the AdbManager that the key files have changed and any in-memory state should be
+ * reloaded.
+ */
+ public abstract void notifyKeyFilesUpdated();
+
+ /**
* Starts adbd for a transport.
*/
public abstract void startAdbdForTransport(byte transportType);
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5..e8e4fc9 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -26,7 +26,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -173,38 +172,63 @@
}
}
- void apply(@NonNull Chunk chunk) {
+ void apply(Chunk chunk) {
+ List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+ List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ List<ProgramList.ListCallback> listCallbacksCopied;
+ List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
if (mIsClosed) return;
mIsComplete = false;
+ listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ for (ProgramSelector.Identifier id : mPrograms.keySet()) {
+ removeLocked(id, removedList);
+ }
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id));
- chunk.getModified().stream().forEach(info -> putLocked(info));
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+ chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
if (chunk.isComplete()) {
mIsComplete = true;
- mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+ }
+ }
+
+ for (int i = 0; i < removedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+ }
+ }
+ for (int i = 0; i < changedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ }
+ }
+ if (chunk.isComplete()) {
+ for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+ onCompleteListenersCopied.get(cbIndex).onComplete();
}
}
}
- private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ private void putLocked(RadioManager.ProgramInfo value,
+ List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
mPrograms.put(Objects.requireNonNull(key), value);
ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ changedIdentifierList.add(sel);
}
- private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ private void removeLocked(ProgramSelector.Identifier key,
+ List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
if (removed == null) return;
ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ removedIdentifierList.add(sel);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 64151d9..9a3957c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2816,10 +2816,6 @@
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
- if (mApplyInsetsRequested) {
- dispatchApplyInsets(host);
- }
-
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
@@ -2883,6 +2879,18 @@
}
}
+ if (mApplyInsetsRequested) {
+ dispatchApplyInsets(host);
+ if (mLayoutRequested) {
+ // Short-circuit catching a new layout request here, so
+ // we don't need to go through two layout passes when things
+ // change due to fitting system windows, which can happen a lot.
+ windowSizeMayChange |= measureHierarchy(host, lp,
+ mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ }
+ }
+
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 12dfa10..8f8993f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,6 +59,26 @@
</LinearLayout>
+ <com.android.systemui.statusbar.KeyguardAffordanceView
+ android:id="@+id/camera_button"
+ android:layout_height="@dimen/keyguard_affordance_height"
+ android:layout_width="@dimen/keyguard_affordance_width"
+ android:layout_gravity="bottom|end"
+ android:src="@drawable/ic_camera_alt_24dp"
+ android:scaleType="center"
+ android:contentDescription="@string/accessibility_camera_button"
+ android:tint="?attr/wallpaperTextColor" />
+
+ <com.android.systemui.statusbar.KeyguardAffordanceView
+ android:id="@+id/left_button"
+ android:layout_height="@dimen/keyguard_affordance_height"
+ android:layout_width="@dimen/keyguard_affordance_width"
+ android:layout_gravity="bottom|start"
+ android:src="@*android:drawable/ic_phone"
+ android:scaleType="center"
+ android:contentDescription="@string/accessibility_phone_button"
+ android:tint="?attr/wallpaperTextColor" />
+
<ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892..2b3d11b 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_ttt_receiver_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/media_ttt_chip_background_receiver"
>
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center"
+ android:layout_gravity="center|bottom"
+ android:alpha="0.0"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 771973c..eff4e00 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -37,6 +37,12 @@
<item>400</item>
</integer-array>
+ <!-- Show mic or phone affordance on Keyguard -->
+ <bool name="config_keyguardShowLeftAffordance">false</bool>
+
+ <!-- Show camera affordance on Keyguard -->
+ <bool name="config_keyguardShowCameraAffordance">false</bool>
+
<!-- decay duration (from size_max -> size), in ms -->
<integer name="navigation_bar_deadzone_hold">333</integer>
<integer name="navigation_bar_deadzone_decay">333</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7f7b871..36a2d64 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -676,6 +676,9 @@
<!-- The minimum background radius when swiping to a side for the camera / phone affordances. -->
<dimen name="keyguard_affordance_min_background_radius">30dp</dimen>
+ <!-- The size of the touch targets on the keyguard for the affordances. -->
+ <dimen name="keyguard_affordance_touch_target_size">120dp</dimen>
+
<!-- The grow amount for the camera and phone circles when hinting -->
<dimen name="hint_grow_amount_sideways">60dp</dimen>
@@ -1057,6 +1060,7 @@
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
the circular chip. -->
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ef672f3..343ec4f6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -313,6 +313,13 @@
<string name="accessibility_scanning_face">Scanning face</string>
<!-- Click action label for accessibility for the smart reply buttons (not shown on-screen).". [CHAR LIMIT=NONE] -->
<string name="accessibility_send_smart_reply">Send</string>
+ <!-- Content description of the manage notification button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
+ <string name="phone_label">open phone</string>
+ <!-- Click action label for accessibility for the voice assist button. This is not shown on-screen and is an accessibility label for the icon which launches the voice assist from the lock screen.[CHAR LIMIT=NONE] -->
+ <string name="voice_assist_label">open voice assist</string>
+ <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
+ <string name="camera_label">open camera</string>
<!-- Button name for "Cancel". [CHAR LIMIT=NONE] -->
<string name="cancel">Cancel</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f62445d..340cde1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -843,7 +843,6 @@
@Override
public void onLaunchAnimationCancelled() {
- setOccluded(true /* occluded */, false /* animate */);
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -916,7 +915,7 @@
mUnoccludeAnimator.cancel();
}
- setOccluded(isKeyguardOccluded, false /* animate */);
+ setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -3220,7 +3219,10 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
super.onAnimationCancelled(isKeyguardOccluded);
- Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
+ setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+
+ Log.d(TAG, "Occlude animation cancelled by WM. "
+ + "Setting occluded state to: " + mOccluded);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index fe1ac80..0f1cdcc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,17 +25,14 @@
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
-import android.widget.LinearLayout
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -65,12 +62,15 @@
private val powerManager: PowerManager,
@LayoutRes private val chipLayoutRes: Int
) {
- /** The window layout parameters we'll use when attaching the view to a window. */
+
+ /**
+ * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+ * all subclasses.
+ */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
- private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = WINDOW_TITLE
@@ -78,6 +78,14 @@
setTrustedOverlay()
}
+ /**
+ * The window layout parameters we'll use when attaching the view to a window.
+ *
+ * Subclasses must override this to provide their specific layout params, and they should use
+ * [commonWindowLayoutParams] as part of their layout params.
+ */
+ internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index a5d763c..f9818f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -24,6 +24,8 @@
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
@@ -36,6 +38,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -69,6 +72,11 @@
powerManager,
R.layout.media_ttt_chip_receiver
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ height = getWindowHeight()
+ gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -131,6 +139,19 @@
)
}
+ override fun animateChipIn(chipView: ViewGroup) {
+ val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+ appIconView.animate()
+ .translationYBy(-1 * getTranslationAmount().toFloat())
+ .setDuration(30.frames)
+ .start()
+ appIconView.animate()
+ .alpha(1f)
+ .setDuration(5.frames)
+ .start()
+
+ }
+
override fun getIconSize(isAppIcon: Boolean): Int? =
context.resources.getDimensionPixelSize(
if (isAppIcon) {
@@ -139,6 +160,17 @@
R.dimen.media_ttt_generic_icon_size_receiver
}
)
+
+ private fun getWindowHeight(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) +
+ // Make the window large enough to accommodate the animation amount
+ getTranslationAmount()
+ }
+
+ /** Returns the amount that the chip will be translated by in its intro animation. */
+ private fun getTranslationAmount(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 943604c..797a770 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,6 +21,7 @@
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -69,6 +70,10 @@
powerManager,
R.layout.media_ttt_chip
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private var currentlyDisplayedChipState: ChipStateSender? = null
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index d25bbbd..2c22bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -427,6 +427,12 @@
void onHintFinished();
+ void onCameraHintStarted();
+
+ void onVoiceAssistHintStarted();
+
+ void onPhoneHintStarted();
+
void onTrackingStopped(boolean expand);
// TODO: Figure out way to remove these.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 38c37f0..9060d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -388,7 +388,8 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanelViewController.launchCamera(source);
+ mNotificationPanelViewController.launchCamera(
+ mCentralSurfaces.isDeviceInteractive() /* animate */, source);
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 4f99dea..5181af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1285,6 +1285,8 @@
backdrop.setScaleY(scale);
});
+ mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
+
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
@@ -3022,7 +3024,8 @@
@Override
public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionFinished();
+ return mNotificationPanelViewController.isLaunchTransitionRunning()
+ || mNotificationPanelViewController.isLaunchTransitionFinished();
}
/**
@@ -3054,7 +3057,11 @@
mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
- hideRunnable.run();
+ if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
+ mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
+ } else {
+ hideRunnable.run();
+ }
}
private void cancelAfterLaunchTransitionRunnables() {
@@ -3063,6 +3070,7 @@
}
mLaunchTransitionEndRunnable = null;
mLaunchTransitionCancelRunnable = null;
+ mNotificationPanelViewController.setLaunchTransitionEndRunnable(null);
}
/**
@@ -3491,6 +3499,24 @@
}
@Override
+ public void onCameraHintStarted() {
+ mFalsingCollector.onCameraHintStarted();
+ mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
+ }
+
+ @Override
+ public void onVoiceAssistHintStarted() {
+ mFalsingCollector.onLeftAffordanceHintStarted();
+ mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
+ }
+
+ @Override
+ public void onPhoneHintStarted() {
+ mFalsingCollector.onLeftAffordanceHintStarted();
+ mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
+ }
+
+ @Override
public void onTrackingStopped(boolean expand) {
}
@@ -3674,7 +3700,8 @@
mWakeUpCoordinator.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
+ mNotificationPanelViewController.launchCamera(
+ false /* animate */, mLastCameraLaunchSource);
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -4316,6 +4343,9 @@
if (!mUserSetup) {
animateCollapseQuickSettings();
}
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
+ }
updateQsExpansionEnabled();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
new file mode 100644
index 0000000..2922b4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.Classifier;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+/**
+ * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
+ */
+public class KeyguardAffordanceHelper {
+
+ public static final long HINT_PHASE1_DURATION = 200;
+ private static final long HINT_PHASE2_DURATION = 350;
+ private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
+ private static final int HINT_CIRCLE_OPEN_DURATION = 500;
+
+ private final Context mContext;
+ private final Callback mCallback;
+
+ private FlingAnimationUtils mFlingAnimationUtils;
+ private VelocityTracker mVelocityTracker;
+ private boolean mSwipingInProgress;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private float mTranslation;
+ private float mTranslationOnDown;
+ private int mTouchSlop;
+ private int mMinTranslationAmount;
+ private int mMinFlingVelocity;
+ private int mHintGrowAmount;
+ private KeyguardAffordanceView mLeftIcon;
+ private KeyguardAffordanceView mRightIcon;
+ private Animator mSwipeAnimator;
+ private final FalsingManager mFalsingManager;
+ private int mMinBackgroundRadius;
+ private boolean mMotionCancelled;
+ private int mTouchTargetSize;
+ private View mTargetedView;
+ private boolean mTouchSlopExeeded;
+ private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSwipeAnimator = null;
+ mSwipingInProgress = false;
+ mTargetedView = null;
+ }
+ };
+ private Runnable mAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAnimationToSideEnded();
+ }
+ };
+
+ KeyguardAffordanceHelper(Callback callback, Context context, FalsingManager falsingManager) {
+ mContext = context;
+ mCallback = callback;
+ initIcons();
+ updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
+ updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
+ mFalsingManager = falsingManager;
+ initDimens();
+ }
+
+ private void initDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(mContext);
+ mTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_min_swipe_amount);
+ mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_affordance_min_background_radius);
+ mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_affordance_touch_target_size);
+ mHintGrowAmount =
+ mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
+ mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
+ 0.4f);
+ }
+
+ private void initIcons() {
+ mLeftIcon = mCallback.getLeftIcon();
+ mRightIcon = mCallback.getRightIcon();
+ updatePreviews();
+ }
+
+ public void updatePreviews() {
+ mLeftIcon.setPreviewView(mCallback.getLeftPreview());
+ mRightIcon.setPreviewView(mCallback.getRightPreview());
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ final float y = event.getY();
+ final float x = event.getX();
+
+ boolean isUp = false;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ View targetView = getIconAtPosition(x, y);
+ if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
+ mMotionCancelled = true;
+ return false;
+ }
+ if (mTargetedView != null) {
+ cancelAnimation();
+ } else {
+ mTouchSlopExeeded = false;
+ }
+ startSwiping(targetView);
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ mTranslationOnDown = mTranslation;
+ initVelocityTracker();
+ trackMovement(event);
+ mMotionCancelled = false;
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mMotionCancelled = true;
+ endMotion(true /* forceSnapBack */, x, y);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ trackMovement(event);
+ float xDist = x - mInitialTouchX;
+ float yDist = y - mInitialTouchY;
+ float distance = (float) Math.hypot(xDist, yDist);
+ if (!mTouchSlopExeeded && distance > mTouchSlop) {
+ mTouchSlopExeeded = true;
+ }
+ if (mSwipingInProgress) {
+ if (mTargetedView == mRightIcon) {
+ distance = mTranslationOnDown - distance;
+ distance = Math.min(0, distance);
+ } else {
+ distance = mTranslationOnDown + distance;
+ distance = Math.max(0, distance);
+ }
+ setTranslation(distance, false /* isReset */, false /* animateReset */);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ isUp = true;
+ case MotionEvent.ACTION_CANCEL:
+ boolean hintOnTheRight = mTargetedView == mRightIcon;
+ trackMovement(event);
+ endMotion(!isUp, x, y);
+ if (!mTouchSlopExeeded && isUp) {
+ mCallback.onIconClicked(hintOnTheRight);
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void startSwiping(View targetView) {
+ mCallback.onSwipingStarted(targetView == mRightIcon);
+ mSwipingInProgress = true;
+ mTargetedView = targetView;
+ }
+
+ private View getIconAtPosition(float x, float y) {
+ if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
+ return mLeftIcon;
+ }
+ if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
+ return mRightIcon;
+ }
+ return null;
+ }
+
+ public boolean isOnAffordanceIcon(float x, float y) {
+ return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
+ }
+
+ private boolean isOnIcon(View icon, float x, float y) {
+ float iconX = icon.getX() + icon.getWidth() / 2.0f;
+ float iconY = icon.getY() + icon.getHeight() / 2.0f;
+ double distance = Math.hypot(x - iconX, y - iconY);
+ return distance <= mTouchTargetSize / 2;
+ }
+
+ private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
+ if (mSwipingInProgress) {
+ flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
+ } else {
+ mTargetedView = null;
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private boolean rightSwipePossible() {
+ return mRightIcon.getVisibility() == View.VISIBLE;
+ }
+
+ private boolean leftSwipePossible() {
+ return mLeftIcon.getVisibility() == View.VISIBLE;
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ public void startHintAnimation(boolean right,
+ Runnable onFinishedListener) {
+ cancelAnimation();
+ startHintAnimationPhase1(right, onFinishedListener);
+ }
+
+ private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
+ final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+ ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ mSwipeAnimator = null;
+ mTargetedView = null;
+ onFinishedListener.run();
+ } else {
+ startUnlockHintAnimationPhase2(right, onFinishedListener);
+ }
+ }
+ });
+ animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ animator.setDuration(HINT_PHASE1_DURATION);
+ animator.start();
+ mSwipeAnimator = animator;
+ mTargetedView = targetView;
+ }
+
+ /**
+ * Phase 2: Move back.
+ */
+ private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
+ ValueAnimator animator = getAnimatorToRadius(right, 0);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSwipeAnimator = null;
+ mTargetedView = null;
+ onFinishedListener.run();
+ }
+ });
+ animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ animator.setDuration(HINT_PHASE2_DURATION);
+ animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
+ animator.start();
+ mSwipeAnimator = animator;
+ }
+
+ private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
+ final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+ ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float newRadius = (float) animation.getAnimatedValue();
+ targetView.setCircleRadiusWithoutAnimation(newRadius);
+ float translation = getTranslationFromRadius(newRadius);
+ mTranslation = right ? -translation : translation;
+ updateIconsFromTranslation(targetView);
+ }
+ });
+ return animator;
+ }
+
+ private void cancelAnimation() {
+ if (mSwipeAnimator != null) {
+ mSwipeAnimator.cancel();
+ }
+ }
+
+ private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
+ float vel = getCurrentVelocity(lastX, lastY);
+
+ // We snap back if the current translation is not far enough
+ boolean snapBack = false;
+ if (mCallback.needsAntiFalsing()) {
+ snapBack = snapBack || mFalsingManager.isFalseTouch(
+ mTargetedView == mRightIcon
+ ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
+ }
+ snapBack = snapBack || isBelowFalsingThreshold();
+
+ // or if the velocity is in the opposite direction.
+ boolean velIsInWrongDirection = vel * mTranslation < 0;
+ snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
+ vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
+ fling(vel, snapBack || forceSnapBack, mTranslation < 0);
+ }
+
+ private boolean isBelowFalsingThreshold() {
+ return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
+ }
+
+ private int getMinTranslationAmount() {
+ float factor = mCallback.getAffordanceFalsingFactor();
+ return (int) (mMinTranslationAmount * factor);
+ }
+
+ private void fling(float vel, final boolean snapBack, boolean right) {
+ float target = right ? -mCallback.getMaxTranslationDistance()
+ : mCallback.getMaxTranslationDistance();
+ target = snapBack ? 0 : target;
+
+ ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
+ mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mTranslation = (float) animation.getAnimatedValue();
+ }
+ });
+ animator.addListener(mFlingEndListener);
+ if (!snapBack) {
+ startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
+ mCallback.onAnimationToSideStarted(right, mTranslation, vel);
+ } else {
+ reset(true);
+ }
+ animator.start();
+ mSwipeAnimator = animator;
+ if (snapBack) {
+ mCallback.onSwipingAborted();
+ }
+ }
+
+ private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
+ boolean right) {
+ KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
+ targetView.finishAnimation(velocity, animationEndRunnable);
+ }
+
+ private void setTranslation(float translation, boolean isReset, boolean animateReset) {
+ translation = rightSwipePossible() ? translation : Math.max(0, translation);
+ translation = leftSwipePossible() ? translation : Math.min(0, translation);
+ float absTranslation = Math.abs(translation);
+ if (translation != mTranslation || isReset) {
+ KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
+ KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
+ float alpha = absTranslation / getMinTranslationAmount();
+
+ // We interpolate the alpha of the other icons to 0
+ float fadeOutAlpha = 1.0f - alpha;
+ fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
+
+ boolean animateIcons = isReset && animateReset;
+ boolean forceNoCircleAnimation = isReset && !animateReset;
+ float radius = getRadiusFromTranslation(absTranslation);
+ boolean slowAnimation = isReset && isBelowFalsingThreshold();
+ if (!isReset) {
+ updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
+ false, false, false, false);
+ } else {
+ updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
+ animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
+ }
+ updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
+ animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
+
+ mTranslation = translation;
+ }
+ }
+
+ private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
+ float absTranslation = Math.abs(mTranslation);
+ float alpha = absTranslation / getMinTranslationAmount();
+
+ // We interpolate the alpha of the other icons to 0
+ float fadeOutAlpha = 1.0f - alpha;
+ fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
+
+ // We interpolate the alpha of the targetView to 1
+ KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
+ updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
+ updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
+ }
+
+ private float getTranslationFromRadius(float circleSize) {
+ float translation = (circleSize - mMinBackgroundRadius)
+ / BACKGROUND_RADIUS_SCALE_FACTOR;
+ return translation > 0.0f ? translation + mTouchSlop : 0.0f;
+ }
+
+ private float getRadiusFromTranslation(float translation) {
+ if (translation <= mTouchSlop) {
+ return 0.0f;
+ }
+ return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
+ }
+
+ public void animateHideLeftRightIcon() {
+ cancelAnimation();
+ updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
+ updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
+ }
+
+ private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
+ boolean animate, boolean slowRadiusAnimation, boolean force,
+ boolean forceNoCircleAnimation) {
+ if (view.getVisibility() != View.VISIBLE && !force) {
+ return;
+ }
+ if (forceNoCircleAnimation) {
+ view.setCircleRadiusWithoutAnimation(circleRadius);
+ } else {
+ view.setCircleRadius(circleRadius, slowRadiusAnimation);
+ }
+ updateIconAlpha(view, alpha, animate);
+ }
+
+ private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
+ float scale = getScale(alpha, view);
+ alpha = Math.min(1.0f, alpha);
+ view.setImageAlpha(alpha, animate);
+ view.setImageScale(scale, animate);
+ }
+
+ private float getScale(float alpha, KeyguardAffordanceView icon) {
+ float scale = alpha / icon.getRestingAlpha() * 0.2f +
+ KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
+ return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
+ }
+
+ private void trackMovement(MotionEvent event) {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
+ }
+
+ private void initVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ }
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+
+ private float getCurrentVelocity(float lastX, float lastY) {
+ if (mVelocityTracker == null) {
+ return 0;
+ }
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float aX = mVelocityTracker.getXVelocity();
+ float aY = mVelocityTracker.getYVelocity();
+ float bX = lastX - mInitialTouchX;
+ float bY = lastY - mInitialTouchY;
+ float bLen = (float) Math.hypot(bX, bY);
+ // Project the velocity onto the distance vector: a * b / |b|
+ float projectedVelocity = (aX * bX + aY * bY) / bLen;
+ if (mTargetedView == mRightIcon) {
+ projectedVelocity = -projectedVelocity;
+ }
+ return projectedVelocity;
+ }
+
+ public void onConfigurationChanged() {
+ initDimens();
+ initIcons();
+ }
+
+ public void onRtlPropertiesChanged() {
+ initIcons();
+ }
+
+ public void reset(boolean animate) {
+ cancelAnimation();
+ setTranslation(0.0f, true /* isReset */, animate);
+ mMotionCancelled = true;
+ if (mSwipingInProgress) {
+ mCallback.onSwipingAborted();
+ mSwipingInProgress = false;
+ }
+ }
+
+ public boolean isSwipingInProgress() {
+ return mSwipingInProgress;
+ }
+
+ public void launchAffordance(boolean animate, boolean left) {
+ if (mSwipingInProgress) {
+ // We don't want to mess with the state if the user is actually swiping already.
+ return;
+ }
+ KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
+ KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
+ startSwiping(targetView);
+
+ // Do not animate the circle expanding if the affordance isn't visible,
+ // otherwise the circle will be meaningless.
+ if (targetView.getVisibility() != View.VISIBLE) {
+ animate = false;
+ }
+
+ if (animate) {
+ fling(0, false, !left);
+ updateIcon(otherView, 0.0f, 0, true, false, true, false);
+ } else {
+ mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
+ mTranslation = left ? mCallback.getMaxTranslationDistance()
+ : mCallback.getMaxTranslationDistance();
+ updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
+ targetView.instantFinishAnimation();
+ mFlingEndListener.onAnimationEnd(null);
+ mAnimationEndRunnable.run();
+ }
+ }
+
+ public interface Callback {
+
+ /**
+ * Notifies the callback when an animation to a side page was started.
+ *
+ * @param rightPage Is the page animated to the right page?
+ */
+ void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
+
+ /**
+ * Notifies the callback the animation to a side page has ended.
+ */
+ void onAnimationToSideEnded();
+
+ float getMaxTranslationDistance();
+
+ void onSwipingStarted(boolean rightIcon);
+
+ void onSwipingAborted();
+
+ void onIconClicked(boolean rightIcon);
+
+ KeyguardAffordanceView getLeftIcon();
+
+ KeyguardAffordanceView getRightIcon();
+
+ View getLeftPreview();
+
+ View getRightPreview();
+
+ /**
+ * @return The factor the minimum swipe amount should be multiplied with.
+ */
+ float getAffordanceFalsingFactor();
+
+ boolean needsAntiFalsing();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 61113a0..93b2e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,30 +16,47 @@
package com.android.systemui.statusbar.phone;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
+import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -47,35 +64,81 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
+import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.IntentButtonProvider;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
+import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
+import com.android.systemui.tuner.TunerService;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
+import java.util.List;
+
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
*/
-public class KeyguardBottomAreaView extends FrameLayout {
+public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
+ KeyguardStateController.Callback,
+ AccessibilityController.AccessibilityStateChangedCallback {
- private static final String TAG = "CentralSurfaces/KeyguardBottomAreaView";
+ final static String TAG = "CentralSurfaces/KeyguardBottomAreaView";
+
+ public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
+ public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
+ public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
+ public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
+
+ public static final String EXTRA_CAMERA_LAUNCH_SOURCE
+ = "com.android.systemui.camera_launch_source";
+
+ private static final String LEFT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
+ private static final String RIGHT_BUTTON_PLUGIN
+ = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
+
+ private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
+ private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
+ // TODO(b/179494051): May no longer be needed
+ private final boolean mShowLeftAffordance;
+ private final boolean mShowCameraAffordance;
+
+ private KeyguardAffordanceView mRightAffordanceView;
+ private KeyguardAffordanceView mLeftAffordanceView;
+
private ImageView mWalletButton;
private ImageView mQRCodeScannerButton;
private ImageView mControlsButton;
private boolean mHasCard = false;
- private final WalletCardRetriever mCardRetriever = new WalletCardRetriever();
+ private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
private QuickAccessWalletController mQuickAccessWalletController;
private QRCodeScannerController mQRCodeScannerController;
private ControlsComponent mControlsComponent;
@@ -85,42 +148,54 @@
private ViewGroup mIndicationArea;
private TextView mIndicationText;
private TextView mIndicationTextBottom;
+ private ViewGroup mPreviewContainer;
private ViewGroup mOverlayContainer;
+ private View mLeftPreview;
+ private View mCameraPreview;
+
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
+ private FlashlightController mFlashlightController;
+ private PreviewInflater mPreviewInflater;
+ private AccessibilityController mAccessibilityController;
private CentralSurfaces mCentralSurfaces;
+ private KeyguardAffordanceHelper mAffordanceHelper;
private FalsingManager mFalsingManager;
+ private boolean mUserSetupComplete;
+ private boolean mLeftIsVoiceAssist;
+ private Drawable mLeftAssistIcon;
+
+ private IntentButton mRightButton = new DefaultRightButton();
+ private Extension<IntentButton> mRightExtension;
+ private String mRightButtonStr;
+ private IntentButton mLeftButton = new DefaultLeftButton();
+ private Extension<IntentButton> mLeftExtension;
+ private String mLeftButtonStr;
private boolean mDozing;
private int mIndicationBottomMargin;
private int mIndicationPadding;
private float mDarkAmount;
private int mBurnInXOffset;
private int mBurnInYOffset;
+ private ActivityIntentHelper mActivityIntentHelper;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final ControlsListingController.ControlsListingCallback mListingCallback =
- serviceInfos -> post(() -> {
- boolean available = !serviceInfos.isEmpty();
+ private ControlsListingController.ControlsListingCallback mListingCallback =
+ new ControlsListingController.ControlsListingCallback() {
+ public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
+ post(() -> {
+ boolean available = !serviceInfos.isEmpty();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
}
- });
-
- private final KeyguardStateController.Callback mKeyguardStateCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- if (mKeyguardStateController.isShowing()) {
- if (mQuickAccessWalletController != null) {
- mQuickAccessWalletController.queryWalletCards(mCardRetriever);
- }
- }
- }
- };
+ };
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -137,8 +212,43 @@
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mShowLeftAffordance = getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
+ mShowCameraAffordance = getResources()
+ .getBoolean(R.bool.config_keyguardShowCameraAffordance);
}
+ private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ String label = null;
+ if (host == mRightAffordanceView) {
+ label = getResources().getString(R.string.camera_label);
+ } else if (host == mLeftAffordanceView) {
+ if (mLeftIsVoiceAssist) {
+ label = getResources().getString(R.string.voice_assist_label);
+ } else {
+ label = getResources().getString(R.string.phone_label);
+ }
+ }
+ info.addAction(new AccessibilityAction(ACTION_CLICK, label));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_CLICK) {
+ if (host == mRightAffordanceView) {
+ launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
+ return true;
+ } else if (host == mLeftAffordanceView) {
+ launchLeftAffordance();
+ return true;
+ }
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ };
+
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
setCentralSurfaces(oldBottomArea.mCentralSurfaces);
@@ -169,7 +279,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
+ new ActivityIntentHelper(mContext));
mOverlayContainer = findViewById(R.id.overlay_container);
+ mRightAffordanceView = findViewById(R.id.camera_button);
+ mLeftAffordanceView = findViewById(R.id.left_button);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
mControlsButton = findViewById(R.id.controls_button);
@@ -181,11 +295,18 @@
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
R.dimen.default_burn_in_prevention_offset);
+ updateCameraVisibility();
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
- mKeyguardStateController.addCallback(mKeyguardStateCallback);
+ mKeyguardStateController.addCallback(this);
setClipChildren(false);
setClipToPadding(false);
+ mRightAffordanceView.setOnClickListener(this);
+ mLeftAffordanceView.setOnClickListener(this);
+ initAccessibility();
mActivityStarter = Dependency.get(ActivityStarter.class);
+ mFlashlightController = Dependency.get(FlashlightController.class);
+ mAccessibilityController = Dependency.get(AccessibilityController.class);
+ mActivityIntentHelper = new ActivityIntentHelper(getContext());
mIndicationPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_area_padding);
@@ -194,18 +315,51 @@
updateControlsVisibility();
}
+ /**
+ * Set the container where the previews are rendered.
+ */
+ public void setPreviewContainer(ViewGroup previewContainer) {
+ mPreviewContainer = previewContainer;
+ inflateCameraPreview();
+ updateLeftAffordance();
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mAccessibilityController.addStateChangedCallback(this);
+ mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
+ .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
+ p -> p.getIntentButton())
+ .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
+ .withDefault(() -> new DefaultRightButton())
+ .withCallback(button -> setRightButton(button))
+ .build();
+ mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
+ .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
+ p -> p.getIntentButton())
+ .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
+ .withDefault(() -> new DefaultLeftButton())
+ .withCallback(button -> setLeftButton(button))
+ .build();
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- mKeyguardStateController.addCallback(mKeyguardStateCallback);
+ getContext().registerReceiverAsUser(mDevicePolicyReceiver,
+ UserHandle.ALL, filter, null, null);
+ mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.addCallback(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mKeyguardStateController.removeCallback(mKeyguardStateCallback);
+ mKeyguardStateController.removeCallback(this);
+ mAccessibilityController.removeStateChangedCallback(this);
+ mRightExtension.destroy();
+ mLeftExtension.destroy();
+ getContext().unregisterReceiver(mDevicePolicyReceiver);
+ mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
if (mQuickAccessWalletController != null) {
mQuickAccessWalletController.unregisterWalletChangeObservers(
@@ -224,6 +378,11 @@
}
}
+ private void initAccessibility() {
+ mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
+ mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -245,7 +404,19 @@
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- ViewGroup.LayoutParams lp = mWalletButton.getLayoutParams();
+ ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
+ lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
+ lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
+ mRightAffordanceView.setLayoutParams(lp);
+ updateRightAffordanceIcon();
+
+ lp = mLeftAffordanceView.getLayoutParams();
+ lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
+ lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
+ mLeftAffordanceView.setLayoutParams(lp);
+ updateLeftAffordanceIcon();
+
+ lp = mWalletButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mWalletButton.setLayoutParams(lp);
@@ -268,8 +439,74 @@
updateAffordanceColors();
}
+ private void updateRightAffordanceIcon() {
+ IconState state = mRightButton.getIcon();
+ mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
+ if (state.drawable != mRightAffordanceView.getDrawable()
+ || state.tint != mRightAffordanceView.shouldTint()) {
+ mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
+ }
+ mRightAffordanceView.setContentDescription(state.contentDescription);
+ }
+
public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
mCentralSurfaces = centralSurfaces;
+ updateCameraVisibility(); // in case onFinishInflate() was called too early
+ }
+
+ public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
+ mAffordanceHelper = affordanceHelper;
+ }
+
+ public void setUserSetupComplete(boolean userSetupComplete) {
+ mUserSetupComplete = userSetupComplete;
+ updateCameraVisibility();
+ updateLeftAffordanceIcon();
+ }
+
+ private Intent getCameraIntent() {
+ return mRightButton.getIntent();
+ }
+
+ /**
+ * Resolves the intent to launch the camera application.
+ */
+ public ResolveInfo resolveCameraIntent() {
+ return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ KeyguardUpdateMonitor.getCurrentUser());
+ }
+
+ private void updateCameraVisibility() {
+ if (mRightAffordanceView == null) {
+ // Things are not set up yet; reply hazy, ask again later
+ return;
+ }
+ mRightAffordanceView.setVisibility(!mDozing && mShowCameraAffordance
+ && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Set an alternate icon for the left assist affordance (replace the mic icon)
+ */
+ public void setLeftAssistIcon(Drawable drawable) {
+ mLeftAssistIcon = drawable;
+ updateLeftAffordanceIcon();
+ }
+
+ private void updateLeftAffordanceIcon() {
+ if (!mShowLeftAffordance || mDozing) {
+ mLeftAffordanceView.setVisibility(GONE);
+ return;
+ }
+
+ IconState state = mLeftButton.getIcon();
+ mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
+ if (state.drawable != mLeftAffordanceView.getDrawable()
+ || state.tint != mLeftAffordanceView.shouldTint()) {
+ mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
+ }
+ mLeftAffordanceView.setContentDescription(state.contentDescription);
}
private void updateWalletVisibility() {
@@ -315,6 +552,73 @@
}
}
+ public boolean isLeftVoiceAssist() {
+ return mLeftIsVoiceAssist;
+ }
+
+ private boolean isPhoneVisible() {
+ PackageManager pm = mContext.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && pm.resolveActivity(PHONE_INTENT, 0) != null;
+ }
+
+ @Override
+ public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
+ mRightAffordanceView.setClickable(touchExplorationEnabled);
+ mLeftAffordanceView.setClickable(touchExplorationEnabled);
+ mRightAffordanceView.setFocusable(accessibilityEnabled);
+ mLeftAffordanceView.setFocusable(accessibilityEnabled);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mRightAffordanceView) {
+ launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
+ } else if (v == mLeftAffordanceView) {
+ launchLeftAffordance();
+ }
+ }
+
+ public void launchCamera(String source) {
+ final Intent intent = getCameraIntent();
+ intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
+ boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
+ intent, KeyguardUpdateMonitor.getCurrentUser());
+ if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the crossfade animation if an orientation change
+ // happens to occur during the launch.
+ ActivityOptions o = ActivityOptions.makeBasic();
+ o.setDisallowEnterPictureInPictureWhileLaunching(true);
+ o.setRotationAnimationHint(
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+ try {
+ ActivityTaskManager.getService().startActivityAsUser(
+ null, getContext().getBasePackageName(),
+ getContext().getAttributionTag(), intent,
+ intent.resolveTypeIfNeeded(getContext().getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
+ UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start camera activity", e);
+ }
+ }
+ });
+ } else {
+ // We need to delay starting the activity because ResolverActivity finishes itself if
+ // launched behind lockscreen.
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ }
+ }
+
public void setDarkAmount(float darkAmount) {
if (darkAmount == mDarkAmount) {
return;
@@ -323,6 +627,77 @@
dozeTimeTick();
}
+ public void launchLeftAffordance() {
+ if (mLeftIsVoiceAssist) {
+ launchVoiceAssist();
+ } else {
+ launchPhone();
+ }
+ }
+
+ @VisibleForTesting
+ void launchVoiceAssist() {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
+ }
+ };
+ if (!mKeyguardStateController.canDismissLockScreen()) {
+ Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
+ } else {
+ boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
+ && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
+ mCentralSurfaces.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
+ dismissShade, false /* afterKeyguardGone */, true /* deferred */);
+ }
+ }
+
+ private boolean canLaunchVoiceAssist() {
+ return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
+ }
+
+ private void launchPhone() {
+ final TelecomManager tm = TelecomManager.from(mContext);
+ if (tm.isInCall()) {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ tm.showInCallScreen(false /* showDialpad */);
+ }
+ });
+ } else {
+ boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
+ && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
+ mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
+ }
+ }
+
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (changedView == this && visibility == VISIBLE) {
+ updateCameraVisibility();
+ }
+ }
+
+ public KeyguardAffordanceView getLeftView() {
+ return mLeftAffordanceView;
+ }
+
+ public KeyguardAffordanceView getRightView() {
+ return mRightAffordanceView;
+ }
+
+ public View getLeftPreview() {
+ return mLeftPreview;
+ }
+
+ public View getRightPreview() {
+ return mCameraPreview;
+ }
+
public View getIndicationArea() {
return mIndicationArea;
}
@@ -332,6 +707,66 @@
return false;
}
+ @Override
+ public void onUnlockedChanged() {
+ updateCameraVisibility();
+ }
+
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()) {
+ if (mQuickAccessWalletController != null) {
+ mQuickAccessWalletController.queryWalletCards(mCardRetriever);
+ }
+ }
+ }
+
+ private void inflateCameraPreview() {
+ if (mPreviewContainer == null) {
+ return;
+ }
+ View previewBefore = mCameraPreview;
+ boolean visibleBefore = false;
+ if (previewBefore != null) {
+ mPreviewContainer.removeView(previewBefore);
+ visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
+ }
+ mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
+ if (mCameraPreview != null) {
+ mPreviewContainer.addView(mCameraPreview);
+ mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
+ }
+ if (mAffordanceHelper != null) {
+ mAffordanceHelper.updatePreviews();
+ }
+ }
+
+ private void updateLeftPreview() {
+ if (mPreviewContainer == null) {
+ return;
+ }
+ View previewBefore = mLeftPreview;
+ if (previewBefore != null) {
+ mPreviewContainer.removeView(previewBefore);
+ }
+
+ if (mLeftIsVoiceAssist) {
+ if (Dependency.get(AssistManager.class).getVoiceInteractorComponentName() != null) {
+ mLeftPreview = mPreviewInflater.inflatePreviewFromService(
+ Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
+ }
+ } else {
+ mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
+ }
+ if (mLeftPreview != null) {
+ mPreviewContainer.addView(mLeftPreview);
+ mLeftPreview.setVisibility(View.INVISIBLE);
+ }
+ if (mAffordanceHelper != null) {
+ mAffordanceHelper.updatePreviews();
+ }
+ }
+
public void startFinishDozeAnimation() {
long delay = 0;
if (mWalletButton.getVisibility() == View.VISIBLE) {
@@ -343,6 +778,13 @@
if (mControlsButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mControlsButton, delay);
}
+ if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mLeftAffordanceView, delay);
+ delay += DOZE_ANIMATION_STAGGER_DELAY;
+ }
+ if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
+ startFinishDozeAnimationElement(mRightAffordanceView, delay);
+ }
}
private void startFinishDozeAnimationElement(View element, long delay) {
@@ -356,9 +798,58 @@
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
+ private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ updateCameraVisibility();
+ }
+ });
+ }
+ };
+
+ private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ updateCameraVisibility();
+ }
+
+ @Override
+ public void onUserUnlocked() {
+ inflateCameraPreview();
+ updateCameraVisibility();
+ updateLeftAffordance();
+ }
+ };
+
+ public void updateLeftAffordance() {
+ updateLeftAffordanceIcon();
+ updateLeftPreview();
+ }
+
+ private void setRightButton(IntentButton button) {
+ mRightButton = button;
+ updateRightAffordanceIcon();
+ updateCameraVisibility();
+ inflateCameraPreview();
+ }
+
+ private void setLeftButton(IntentButton button) {
+ mLeftButton = button;
+ if (!(mLeftButton instanceof DefaultLeftButton)) {
+ mLeftIsVoiceAssist = false;
+ }
+ updateLeftAffordance();
+ }
+
public void setDozing(boolean dozing, boolean animate) {
mDozing = dozing;
+ updateCameraVisibility();
+ updateLeftAffordanceIcon();
updateWalletVisibility();
updateControlsVisibility();
updateQRCodeButtonVisibility();
@@ -397,12 +888,77 @@
* Sets the alpha of the indication areas and affordances, excluding the lock icon.
*/
public void setAffordanceAlpha(float alpha) {
+ mLeftAffordanceView.setAlpha(alpha);
+ mRightAffordanceView.setAlpha(alpha);
mIndicationArea.setAlpha(alpha);
mWalletButton.setAlpha(alpha);
mQRCodeScannerButton.setAlpha(alpha);
mControlsButton.setAlpha(alpha);
}
+ private class DefaultLeftButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ mLeftIsVoiceAssist = canLaunchVoiceAssist();
+ if (mLeftIsVoiceAssist) {
+ mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance;
+ if (mLeftAssistIcon == null) {
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
+ } else {
+ mIconState.drawable = mLeftAssistIcon;
+ }
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_voice_assist_button);
+ } else {
+ mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance
+ && isPhoneVisible();
+ mIconState.drawable = mContext.getDrawable(
+ com.android.internal.R.drawable.ic_phone);
+ mIconState.contentDescription = mContext.getString(
+ R.string.accessibility_phone_button);
+ }
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return PHONE_INTENT;
+ }
+ }
+
+ private class DefaultRightButton implements IntentButton {
+
+ private IconState mIconState = new IconState();
+
+ @Override
+ public IconState getIcon() {
+ boolean isCameraDisabled = (mCentralSurfaces != null)
+ && !mCentralSurfaces.isCameraAllowedByAdmin();
+ mIconState.isVisible = !isCameraDisabled
+ && mShowCameraAffordance
+ && mUserSetupComplete
+ && resolveCameraIntent() != null;
+ mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
+ mIconState.contentDescription =
+ mContext.getString(R.string.accessibility_camera_button);
+ return mIconState;
+ }
+
+ @Override
+ public Intent getIntent() {
+ boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
+ boolean secure = mKeyguardStateController.isMethodSecure();
+ if (secure && !canDismissLs) {
+ return CameraIntents.getSecureCameraIntent(getContext());
+ } else {
+ return CameraIntents.getInsecureCameraIntent(getContext());
+ }
+ }
+ }
+
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int bottom = insets.getDisplayCutout() != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c757cba..fbbb587 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -50,6 +50,8 @@
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
@@ -143,6 +145,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -249,6 +252,9 @@
private final OnOverscrollTopChangedListener
mOnOverscrollTopChangedListener =
new OnOverscrollTopChangedListener();
+ private final KeyguardAffordanceHelperCallback
+ mKeyguardAffordanceHelperCallback =
+ new KeyguardAffordanceHelperCallback();
private final OnEmptySpaceClickListener
mOnEmptySpaceClickListener =
new OnEmptySpaceClickListener();
@@ -332,6 +338,8 @@
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
+ private ViewGroup mPreviewContainer;
+ private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
@@ -429,6 +437,8 @@
*/
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
+ private boolean mIsLaunchTransitionRunning;
+ private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
private ValueAnimator mQsSizeChangeAnimator;
@@ -445,8 +455,10 @@
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
private boolean mLaunchingAffordance;
+ private boolean mAffordanceHasPreview;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
+ private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
private Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
@@ -479,6 +491,7 @@
private float mLinearDarkAmount;
private boolean mPulsing;
+ private boolean mUserSetupComplete;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
/**
@@ -995,6 +1008,8 @@
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ mPreviewContainer = mView.findViewById(R.id.preview_container);
+ mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
initBottomArea();
@@ -1018,6 +1033,7 @@
mView.setRtlChangeListener(layoutDirection -> {
if (layoutDirection != mOldLayoutDirection) {
+ mAffordanceHelper.onRtlPropertiesChanged();
mOldLayoutDirection = layoutDirection;
}
});
@@ -1251,6 +1267,7 @@
mKeyguardBottomArea = (KeyguardBottomAreaView) mLayoutInflater.inflate(
R.layout.keyguard_bottom_area, mView, false);
mKeyguardBottomArea.initFrom(oldBottomArea);
+ mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
@@ -1287,7 +1304,11 @@
}
private void initBottomArea() {
+ mAffordanceHelper = new KeyguardAffordanceHelper(
+ mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
+ mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
mKeyguardBottomArea.setCentralSurfaces(mCentralSurfaces);
+ mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
mKeyguardBottomArea.setFalsingManager(mFalsingManager);
mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
mKeyguardBottomArea.initControls(mControlsComponent);
@@ -1652,6 +1673,10 @@
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
mBlockTouches = false;
+ if (!mLaunchingAffordance) {
+ mAffordanceHelper.reset(false);
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+ }
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -2194,6 +2219,11 @@
return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
}
+ @Override
+ protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
+ return !mAffordanceHelper.isOnAffordanceIcon(x, y);
+ }
+
private void onQsTouch(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
@@ -3358,6 +3388,9 @@
mQsExpandImmediate = true;
setShowShelfOnly(true);
}
+ if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+ mAffordanceHelper.animateHideLeftRightIcon();
+ }
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
cancelPendingPanelCollapse();
}
@@ -3371,6 +3404,12 @@
true /* animate */);
}
mNotificationStackScrollLayoutController.onPanelTrackingStopped();
+ if (expand && (mBarState == KEYGUARD
+ || mBarState == StatusBarState.SHADE_LOCKED)) {
+ if (!mHintAnimationRunning) {
+ mAffordanceHelper.reset(true);
+ }
+ }
// If we unlocked from a swipe, the user's finger might still be down after the
// unlock animation ends. We need to wait until ACTION_UP to enable blurs again.
@@ -3443,6 +3482,10 @@
return mIsLaunchTransitionFinished;
}
+ public boolean isLaunchTransitionRunning() {
+ return mIsLaunchTransitionRunning;
+ }
+
@Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
@@ -3461,6 +3504,10 @@
}
}
+ public void setLaunchTransitionEndRunnable(Runnable r) {
+ mLaunchAnimationEndRunnable = r;
+ }
+
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
if (!mDozing && animate) {
@@ -3639,8 +3686,30 @@
&& mBarState == StatusBarState.SHADE;
}
- /** Launches the camera. */
- public void launchCamera(int source) {
+ public void launchCamera(boolean animate, int source) {
+ if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
+ } else {
+
+ // Default.
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+ }
+
+ // If we are launching it when we are occluded already we don't want it to animate,
+ // nor setting these flags, since the occluded state doesn't change anymore, hence it's
+ // never reset.
+ if (!isFullyCollapsed()) {
+ setLaunchingAffordance(true);
+ } else {
+ animate = false;
+ }
+ mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
+ mAffordanceHelper.launchAffordance(
+ animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
}
public void onAffordanceLaunchEnded() {
@@ -3653,6 +3722,9 @@
*/
private void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
+ mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
+ mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
+ launchingAffordance);
mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
}
@@ -3660,14 +3732,24 @@
* Return true when a bottom affordance is launching an occluded activity with a splash screen.
*/
public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance;
+ return mLaunchingAffordance && mAffordanceHasPreview;
}
/**
* Whether the camera application can be launched for the camera launch gesture.
*/
public boolean canCameraGestureBeLaunched() {
- return false;
+ if (!mCentralSurfaces.isCameraAllowedByAdmin()) {
+ return false;
+ }
+
+ ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
+ String
+ packageToLaunch =
+ (resolveInfo == null || resolveInfo.activityInfo == null) ? null
+ : resolveInfo.activityInfo.packageName;
+ return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
+ packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
}
/**
@@ -3756,6 +3838,9 @@
@Override
public void setTouchAndAnimationDisabled(boolean disabled) {
super.setTouchAndAnimationDisabled(disabled);
+ if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
+ mAffordanceHelper.reset(false /* animate */);
+ }
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -3838,6 +3923,11 @@
return mKeyguardBottomArea;
}
+ public void setUserSetupComplete(boolean userSetupComplete) {
+ mUserSetupComplete = userSetupComplete;
+ mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
+ }
+
public void applyLaunchAnimationProgress(float linearProgress) {
boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
@@ -4191,6 +4281,10 @@
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
boolean handled = false;
+ if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
+ && mBarState != StatusBarState.SHADE && !mDozing) {
+ handled |= mAffordanceHelper.onTouchEvent(event);
+ }
if (mOnlyAffordanceInThisMotion) {
return true;
}
@@ -4433,6 +4527,139 @@
}
}
+ private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
+ @Override
+ public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
+ boolean
+ start =
+ mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
+ : !rightPage;
+ mIsLaunchTransitionRunning = true;
+ mLaunchAnimationEndRunnable = null;
+ float displayDensity = mCentralSurfaces.getDisplayDensity();
+ int lengthDp = Math.abs((int) (translation / displayDensity));
+ int velocityDp = Math.abs((int) (vel / displayDensity));
+ if (start) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER);
+ mFalsingCollector.onLeftAffordanceOn();
+ if (mFalsingCollector.shouldEnforceBouncer()) {
+ mCentralSurfaces.executeRunnableDismissingKeyguard(
+ () -> mKeyguardBottomArea.launchLeftAffordance(), null,
+ true /* dismissShade */, false /* afterKeyguardGone */,
+ true /* deferred */);
+ } else {
+ mKeyguardBottomArea.launchLeftAffordance();
+ }
+ } else {
+ if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
+ mLastCameraLaunchSource)) {
+ mLockscreenGestureLogger.write(
+ MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA);
+ }
+ mFalsingCollector.onCameraOn();
+ if (mFalsingCollector.shouldEnforceBouncer()) {
+ mCentralSurfaces.executeRunnableDismissingKeyguard(
+ () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
+ true /* dismissShade */, false /* afterKeyguardGone */,
+ true /* deferred */);
+ } else {
+ mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
+ }
+ }
+ mCentralSurfaces.startLaunchTransitionTimeout();
+ mBlockTouches = true;
+ }
+
+ @Override
+ public void onAnimationToSideEnded() {
+ mIsLaunchTransitionRunning = false;
+ mIsLaunchTransitionFinished = true;
+ if (mLaunchAnimationEndRunnable != null) {
+ mLaunchAnimationEndRunnable.run();
+ mLaunchAnimationEndRunnable = null;
+ }
+ mCentralSurfaces.readyForKeyguardDone();
+ }
+
+ @Override
+ public float getMaxTranslationDistance() {
+ return (float) Math.hypot(mView.getWidth(), getHeight());
+ }
+
+ @Override
+ public void onSwipingStarted(boolean rightIcon) {
+ mFalsingCollector.onAffordanceSwipingStarted(rightIcon);
+ mView.requestDisallowInterceptTouchEvent(true);
+ mOnlyAffordanceInThisMotion = true;
+ mQsTracking = false;
+ }
+
+ @Override
+ public void onSwipingAborted() {
+ mFalsingCollector.onAffordanceSwipingAborted();
+ }
+
+ @Override
+ public void onIconClicked(boolean rightIcon) {
+ if (mHintAnimationRunning) {
+ return;
+ }
+ mHintAnimationRunning = true;
+ mAffordanceHelper.startHintAnimation(rightIcon, () -> {
+ mHintAnimationRunning = false;
+ mCentralSurfaces.onHintFinished();
+ });
+ rightIcon =
+ mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
+ : rightIcon;
+ if (rightIcon) {
+ mCentralSurfaces.onCameraHintStarted();
+ } else {
+ if (mKeyguardBottomArea.isLeftVoiceAssist()) {
+ mCentralSurfaces.onVoiceAssistHintStarted();
+ } else {
+ mCentralSurfaces.onPhoneHintStarted();
+ }
+ }
+ }
+
+ @Override
+ public KeyguardAffordanceView getLeftIcon() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
+ }
+
+ @Override
+ public KeyguardAffordanceView getRightIcon() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
+ }
+
+ @Override
+ public View getLeftPreview() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
+ }
+
+ @Override
+ public View getRightPreview() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
+ }
+
+ @Override
+ public float getAffordanceFalsingFactor() {
+ return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ }
+
+ @Override
+ public boolean needsAntiFalsing() {
+ return mBarState == KEYGUARD;
+ }
+ }
+
private class OnEmptySpaceClickListener implements
NotificationStackScrollLayout.OnEmptySpaceClickListener {
@Override
@@ -4891,6 +5118,15 @@
}
}
+ private class OnConfigurationChangedListener extends
+ PanelViewController.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mAffordanceHelper.onConfigurationChanged();
+ }
+ }
+
private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
// the same types of insets that are handled in NotificationShadeWindowView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index d2fc1af..ed12b00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -484,6 +484,8 @@
protected abstract boolean shouldGestureWaitForTouchSlop();
+ protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
+
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mCentralSurfaces.onTrackingStopped(expand);
@@ -1331,7 +1333,7 @@
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = true;
+ mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
}
switch (event.getActionMasked()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
new file mode 100644
index 0000000..3d31714
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.ActivityIntentHelper;
+import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
+
+import java.util.List;
+
+/**
+ * Utility class to inflate previews for phone and camera affordance.
+ */
+public class PreviewInflater {
+
+ private static final String TAG = "PreviewInflater";
+
+ private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
+ private final ActivityIntentHelper mActivityIntentHelper;
+
+ private Context mContext;
+ private LockPatternUtils mLockPatternUtils;
+
+ public PreviewInflater(Context context, LockPatternUtils lockPatternUtils,
+ ActivityIntentHelper activityIntentHelper) {
+ mContext = context;
+ mLockPatternUtils = lockPatternUtils;
+ mActivityIntentHelper = activityIntentHelper;
+ }
+
+ public View inflatePreview(Intent intent) {
+ WidgetInfo info = getWidgetInfo(intent);
+ return inflatePreview(info);
+ }
+
+ public View inflatePreviewFromService(ComponentName componentName) {
+ WidgetInfo info = getWidgetInfoFromService(componentName);
+ return inflatePreview(info);
+ }
+
+ private KeyguardPreviewContainer inflatePreview(WidgetInfo info) {
+ if (info == null) {
+ return null;
+ }
+ View v = inflateWidgetView(info);
+ if (v == null) {
+ return null;
+ }
+ KeyguardPreviewContainer container = new KeyguardPreviewContainer(mContext, null);
+ container.addView(v);
+ return container;
+ }
+
+ private View inflateWidgetView(WidgetInfo widgetInfo) {
+ View widgetView = null;
+ try {
+ Context appContext = mContext.createPackageContext(
+ widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
+ LayoutInflater appInflater = (LayoutInflater)
+ appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ appInflater = appInflater.cloneInContext(appContext);
+ widgetView = appInflater.inflate(widgetInfo.layoutId, null, false);
+ } catch (PackageManager.NameNotFoundException|RuntimeException e) {
+ Log.w(TAG, "Error creating widget view", e);
+ }
+ return widgetView;
+ }
+
+ private WidgetInfo getWidgetInfoFromService(ComponentName componentName) {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the preview specified in the service meta-data
+ try {
+ Bundle metaData = packageManager.getServiceInfo(
+ componentName, PackageManager.GET_META_DATA).metaData;
+ return getWidgetInfoFromMetaData(componentName.getPackageName(), metaData);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to load preview; " + componentName.flattenToShortString()
+ + " not found", e);
+ }
+ return null;
+ }
+
+ private WidgetInfo getWidgetInfoFromMetaData(String contextPackage,
+ Bundle metaData) {
+ if (metaData == null) {
+ return null;
+ }
+ int layoutId = metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
+ if (layoutId == 0) {
+ return null;
+ }
+ WidgetInfo info = new WidgetInfo();
+ info.contextPackage = contextPackage;
+ info.layoutId = layoutId;
+ return info;
+ }
+
+ private WidgetInfo getWidgetInfo(Intent intent) {
+ PackageManager packageManager = mContext.getPackageManager();
+ int flags = PackageManager.MATCH_DEFAULT_ONLY
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+ intent, flags, KeyguardUpdateMonitor.getCurrentUser());
+ if (appList.size() == 0) {
+ return null;
+ }
+ ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
+ flags | PackageManager.GET_META_DATA,
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (mActivityIntentHelper.wouldLaunchResolverActivity(resolved, appList)) {
+ return null;
+ }
+ if (resolved == null || resolved.activityInfo == null) {
+ return null;
+ }
+ return getWidgetInfoFromMetaData(resolved.activityInfo.packageName,
+ resolved.activityInfo.metaData);
+ }
+
+ private static class WidgetInfo {
+ String contextPackage;
+ int layoutId;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 1527f0d..2eb4783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -371,11 +371,9 @@
powerManager,
R.layout.media_ttt_chip
) {
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
- }
-
- override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+ override val windowLayoutParams = commonWindowLayoutParams
+ override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ChipInfo : ChipInfoCommon {
@@ -386,4 +384,4 @@
private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47
\ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 266f0e9..2faff0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -848,6 +848,7 @@
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
@@ -884,6 +885,7 @@
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 4b557dc..31465f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -12,12 +12,12 @@
import com.android.systemui.statusbar.policy.FlashlightController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
-import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -51,5 +51,6 @@
null, false) as KeyguardBottomAreaView
other.initFrom(mKeyguardBottomArea)
+ other.launchVoiceAssist()
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 046af95..79c7e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -106,6 +106,7 @@
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -400,6 +401,8 @@
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
+ when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
+ when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 297d28d..56990ed 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -19,7 +19,7 @@
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -102,11 +102,26 @@
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
+ * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keys
* that are authorized to connect to the ADB service itself.
+ *
+ * <p>The AdbDebuggingManager controls two files:
+ * <ol>
+ * <li>adb_keys
+ * <li>adb_temp_keys.xml
+ * </ol>
+ *
+ * <p>The ADB Daemon (adbd) reads <em>only</em> the adb_keys file for authorization. Public keys
+ * from registered hosts are stored in adb_keys, one entry per line.
+ *
+ * <p>AdbDebuggingManager also keeps adb_temp_keys.xml, which is used for two things
+ * <ol>
+ * <li>Removing unused keys from the adb_keys file
+ * <li>Managing authorized WiFi access points for ADB over WiFi
+ * </ol>
*/
public class AdbDebuggingManager {
- private static final String TAG = "AdbDebuggingManager";
+ private static final String TAG = AdbDebuggingManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean MDNS_DEBUG = false;
@@ -118,18 +133,20 @@
// as a subsequent connection occurs within the allowed duration.
private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
private static final int BUFFER_SIZE = 65536;
+ private static final Ticker SYSTEM_TICKER = () -> System.currentTimeMillis();
private final Context mContext;
private final ContentResolver mContentResolver;
- private final Handler mHandler;
- private AdbDebuggingThread mThread;
+ @VisibleForTesting final AdbDebuggingHandler mHandler;
+ @Nullable private AdbDebuggingThread mThread;
private boolean mAdbUsbEnabled = false;
private boolean mAdbWifiEnabled = false;
private String mFingerprints;
// A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
- private final Map<String, Integer> mConnectedKeys;
- private String mConfirmComponent;
- private final File mTestUserKeyFile;
+ private final Map<String, Integer> mConnectedKeys = new HashMap<>();
+ private final String mConfirmComponent;
+ @Nullable private final File mUserKeyFile;
+ @Nullable private final File mTempKeysFile;
private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
"persist.adb.tls_server.enable";
@@ -138,37 +155,44 @@
private static final int PAIRING_CODE_LENGTH = 6;
private PairingThread mPairingThread = null;
// A list of keys connected via wifi
- private final Set<String> mWifiConnectedKeys;
+ private final Set<String> mWifiConnectedKeys = new HashSet<>();
// The current info of the adbwifi connection.
- private AdbConnectionInfo mAdbConnectionInfo;
+ private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
// Polls for a tls port property when adb wifi is enabled
private AdbConnectionPortPoller mConnectionPortPoller;
private final PortListenerImpl mPortListener = new PortListenerImpl();
+ private final Ticker mTicker;
public AdbDebuggingManager(Context context) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
- mContext = context;
- mContentResolver = mContext.getContentResolver();
- mTestUserKeyFile = null;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ this(
+ context,
+ /* confirmComponent= */ null,
+ getAdbFile(ADB_KEYS_FILE),
+ getAdbFile(ADB_TEMP_KEYS_FILE),
+ /* adbDebuggingThread= */ null,
+ SYSTEM_TICKER);
}
/**
* Constructor that accepts the component to be invoked to confirm if the user wants to allow
* an adb connection from the key.
*/
- @TestApi
- protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+ @VisibleForTesting
+ AdbDebuggingManager(
+ Context context,
+ String confirmComponent,
+ File testUserKeyFile,
+ File tempKeysFile,
+ AdbDebuggingThread adbDebuggingThread,
+ Ticker ticker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mConfirmComponent = confirmComponent;
- mTestUserKeyFile = testUserKeyFile;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ mUserKeyFile = testUserKeyFile;
+ mTempKeysFile = tempKeysFile;
+ mThread = adbDebuggingThread;
+ mTicker = ticker;
+ mHandler = new AdbDebuggingHandler(FgThread.get().getLooper(), mThread);
}
static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
@@ -189,8 +213,7 @@
// consisting of only letters, digits, and hyphens, must begin and end
// with a letter or digit, must not contain consecutive hyphens, and
// must contain at least one letter.
- @VisibleForTesting
- static final String SERVICE_PROTOCOL = "adb-tls-pairing";
+ @VisibleForTesting static final String SERVICE_PROTOCOL = "adb-tls-pairing";
private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL);
private int mPort;
@@ -352,16 +375,24 @@
}
}
- class AdbDebuggingThread extends Thread {
+ @VisibleForTesting
+ static class AdbDebuggingThread extends Thread {
private boolean mStopped;
private LocalSocket mSocket;
private OutputStream mOutputStream;
private InputStream mInputStream;
+ private Handler mHandler;
+ @VisibleForTesting
AdbDebuggingThread() {
super(TAG);
}
+ @VisibleForTesting
+ void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "Entering thread");
@@ -536,7 +567,7 @@
}
}
- class AdbConnectionInfo {
+ private static class AdbConnectionInfo {
private String mBssid;
private String mSsid;
private int mPort;
@@ -743,11 +774,14 @@
// Notification when adbd socket is disconnected.
static final int MSG_ADBD_SOCKET_DISCONNECTED = 27;
+ // === Messages from other parts of the system
+ private static final int MESSAGE_KEY_FILES_UPDATED = 28;
+
// === Messages we can send to adbd ===========
static final String MSG_DISCONNECT_DEVICE = "DD";
static final String MSG_DISABLE_ADBDWIFI = "DA";
- private AdbKeyStore mAdbKeyStore;
+ @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore;
// Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
// connection unless all transport types are disconnected.
@@ -762,19 +796,19 @@
}
};
- AdbDebuggingHandler(Looper looper) {
- super(looper);
- }
-
- /**
- * Constructor that accepts the AdbDebuggingThread to which responses should be sent
- * and the AdbKeyStore to be used to store the temporary grants.
- */
- @TestApi
- AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
+ /** Constructor that accepts the AdbDebuggingThread to which responses should be sent. */
+ @VisibleForTesting
+ AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread) {
super(looper);
mThread = thread;
- mAdbKeyStore = adbKeyStore;
+ }
+
+ /** Initialize the AdbKeyStore so tests can grab mAdbKeyStore immediately. */
+ @VisibleForTesting
+ void initKeyStore() {
+ if (mAdbKeyStore == null) {
+ mAdbKeyStore = new AdbKeyStore();
+ }
}
// Show when at least one device is connected.
@@ -805,6 +839,7 @@
registerForAuthTimeChanges();
mThread = new AdbDebuggingThread();
+ mThread.setHandler(mHandler);
mThread.start();
mAdbKeyStore.updateKeyStore();
@@ -825,8 +860,7 @@
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
- mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(entry.getKey(), mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
mConnectedKeys.clear();
@@ -836,9 +870,7 @@
}
public void handleMessage(Message msg) {
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
switch (msg.what) {
case MESSAGE_ADB_ENABLED:
@@ -873,7 +905,7 @@
if (!mConnectedKeys.containsKey(key)) {
mConnectedKeys.put(key, 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -920,9 +952,7 @@
mConnectedKeys.clear();
// If the key store has not yet been instantiated then do so now; this avoids
// the unnecessary creation of the key store when adb is not enabled.
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
mWifiConnectedKeys.clear();
mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore();
@@ -937,7 +967,8 @@
alwaysAllow = true;
int refcount = mConnectedKeys.get(key) - 1;
if (refcount == 0) {
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(
+ key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
mConnectedKeys.remove(key);
@@ -963,7 +994,7 @@
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
@@ -984,7 +1015,7 @@
} else {
mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
@@ -1206,6 +1237,10 @@
}
break;
}
+ case MESSAGE_KEY_FILES_UPDATED: {
+ mAdbKeyStore.reloadKeyMap();
+ break;
+ }
}
}
@@ -1377,8 +1412,7 @@
AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
UserHandle.ALL);
// Add the key into the keystore
- mAdbKeyStore.setLastConnectionTime(publicKey,
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(publicKey, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -1449,19 +1483,13 @@
extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
int currentUserId = ActivityManager.getCurrentUser();
- UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
- String componentString;
- if (userInfo.isAdmin()) {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- } else {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- }
+ String componentString =
+ Resources.getSystem().getString(
+ R.string.config_customAdbWifiNetworkConfirmationComponent);
ComponentName componentName = ComponentName.unflattenFromString(componentString);
+ UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
- || startConfirmationService(componentName, userInfo.getUserHandle(),
- extras)) {
+ || startConfirmationService(componentName, userInfo.getUserHandle(), extras)) {
return;
}
Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
@@ -1543,7 +1571,7 @@
/**
* Returns a new File with the specified name in the adb directory.
*/
- private File getAdbFile(String fileName) {
+ private static File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -1556,66 +1584,38 @@
}
File getAdbTempKeysFile() {
- return getAdbFile(ADB_TEMP_KEYS_FILE);
+ return mTempKeysFile;
}
File getUserKeyFile() {
- return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
- }
-
- private void writeKey(String key) {
- try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- FileOutputStream fo = new FileOutputStream(keyFile, true);
- fo.write(key.getBytes());
- fo.write('\n');
- fo.close();
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
- } catch (IOException ex) {
- Slog.e(TAG, "Error writing key:" + ex);
- }
+ return mUserKeyFile;
}
private void writeKeys(Iterable<String> keys) {
- AtomicFile atomicKeyFile = null;
+ if (mUserKeyFile == null) {
+ return;
+ }
+
+ AtomicFile atomicKeyFile = new AtomicFile(mUserKeyFile);
+ // Note: Do not use a try-with-resources with the FileOutputStream, because AtomicFile
+ // requires that it's cleaned up with AtomicFile.failWrite();
FileOutputStream fo = null;
try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- atomicKeyFile = new AtomicFile(keyFile);
fo = atomicKeyFile.startWrite();
for (String key : keys) {
fo.write(key.getBytes());
fo.write('\n');
}
atomicKeyFile.finishWrite(fo);
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing keys: " + ex);
- if (atomicKeyFile != null) {
- atomicKeyFile.failWrite(fo);
- }
+ atomicKeyFile.failWrite(fo);
+ return;
}
- }
- private void deleteKeyFile() {
- File keyFile = getUserKeyFile();
- if (keyFile != null) {
- keyFile.delete();
- }
+ FileUtils.setPermissions(
+ mUserKeyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
}
/**
@@ -1745,6 +1745,13 @@
}
/**
+ * Notify that they key files were updated so the AdbKeyManager reloads the keys.
+ */
+ public void notifyKeyFilesUpdated() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_KEY_FILES_UPDATED);
+ }
+
+ /**
* Sends a message to the handler to persist the keystore.
*/
private void sendPersistKeyStoreMessage() {
@@ -1778,7 +1785,7 @@
try {
dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE,
- FileUtils.readTextFile(getAdbTempKeysFile(), 0, null));
+ FileUtils.readTextFile(mTempKeysFile, 0, null));
} catch (IOException e) {
Slog.i(TAG, "Cannot read keystore: ", e);
}
@@ -1792,12 +1799,12 @@
* ADB_ALLOWED_CONNECTION_TIME setting.
*/
class AdbKeyStore {
- private Map<String, Long> mKeyMap;
- private Set<String> mSystemKeys;
- private File mKeyFile;
private AtomicFile mAtomicKeyFile;
- private List<String> mTrustedNetworks;
+ private final Set<String> mSystemKeys;
+ private final Map<String, Long> mKeyMap = new HashMap<>();
+ private final List<String> mTrustedNetworks = new ArrayList<>();
+
private static final int KEYSTORE_VERSION = 1;
private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
private static final String XML_KEYSTORE_START_TAG = "keyStore";
@@ -1819,26 +1826,22 @@
public static final long NO_PREVIOUS_CONNECTION = 0;
/**
- * Constructor that uses the default location for the persistent adb keystore.
+ * Create an AdbKeyStore instance.
+ *
+ * <p>Upon creation, we parse {@link #mTempKeysFile} to determine authorized WiFi APs and
+ * retrieve the map of stored ADB keys and their last connected times. After that, we read
+ * the {@link #mUserKeyFile}, and any keys that exist in that file that do not exist in the
+ * map are added to the map (for backwards compatibility).
*/
AdbKeyStore() {
- init();
- }
-
- /**
- * Constructor that uses the specified file as the location for the persistent adb keystore.
- */
- AdbKeyStore(File keyFile) {
- mKeyFile = keyFile;
- init();
- }
-
- private void init() {
initKeyFile();
- mKeyMap = getKeyMap();
- mTrustedNetworks = getTrustedNetworks();
+ readTempKeysFile();
mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
- addUserKeysToKeyStore();
+ addExistingUserKeysToKeyStore();
+ }
+
+ public void reloadKeyMap() {
+ readTempKeysFile();
}
public void addTrustedNetwork(String bssid) {
@@ -1877,7 +1880,6 @@
public void removeKey(String key) {
if (mKeyMap.containsKey(key)) {
mKeyMap.remove(key);
- writeKeys(mKeyMap.keySet());
sendPersistKeyStoreMessage();
}
}
@@ -1886,12 +1888,9 @@
* Initializes the key file that will be used to persist the adb grants.
*/
private void initKeyFile() {
- if (mKeyFile == null) {
- mKeyFile = getAdbTempKeysFile();
- }
- // getAdbTempKeysFile can return null if the adb file cannot be obtained
- if (mKeyFile != null) {
- mAtomicKeyFile = new AtomicFile(mKeyFile);
+ // mTempKeysFile can be null if the adb file cannot be obtained
+ if (mTempKeysFile != null) {
+ mAtomicKeyFile = new AtomicFile(mTempKeysFile);
}
}
@@ -1932,201 +1931,108 @@
}
/**
- * Returns the key map with the keys and last connection times from the key file.
+ * Update the key map and the trusted networks list with values parsed from the temp keys
+ * file.
*/
- private Map<String, Long> getKeyMap() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
+ private void readTempKeysFile() {
+ mKeyMap.clear();
+ mTrustedNetworks.clear();
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for reading");
+ return;
}
}
if (!mAtomicKeyFile.exists()) {
- return keyMap;
+ return;
}
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return keyMap;
- }
+ TypedXmlPullParser parser;
+ try {
+ parser = Xml.resolvePullParser(keyStream);
+ XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+
int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
Slog.e(TAG, "Keystore version=" + keystoreVersion
+ " not supported (max_supported="
+ MAX_SUPPORTED_KEYSTORE_VERSION + ")");
- return keyMap;
+ return;
}
+ } catch (XmlPullParserException e) {
+ // This could be because the XML document doesn't start with
+ // XML_KEYSTORE_START_TAG. Try again, instead just starting the document with
+ // the adbKey tag (the old format).
+ parser = Xml.resolvePullParser(keyStream);
}
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- keyMap.put(key, connectionTime);
- }
+ readKeyStoreContents(parser);
} catch (IOException e) {
Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
} catch (XmlPullParserException e) {
- Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
- // The file could be written in a format prior to introducing keystore tag.
- return getKeyMapBeforeKeystoreVersion();
+ Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
}
- return keyMap;
}
-
- /**
- * Returns the key map with the keys and last connection times from the key file.
- * This implementation was prior to adding the XML_KEYSTORE_START_TAG.
- */
- private Map<String, Long> getKeyMapBeforeKeystoreVersion() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
+ private void readKeyStoreContents(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // This parser is very forgiving. For backwards-compatibility, we simply iterate through
+ // all the tags in the file, skipping over anything that's not an <adbKey> tag or a
+ // <wifiAP> tag. Invalid tags (such as ones that don't have a valid "lastConnection"
+ // attribute) are simply ignored.
+ while ((parser.next()) != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (XML_TAG_ADB_KEY.equals(tagName)) {
+ addAdbKeyToKeyMap(parser);
+ } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) {
+ addTrustedNetworkToTrustedNetworks(parser);
+ } else {
+ Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized.");
}
+ XmlUtils.skipCurrentTag(parser);
}
- if (!mAtomicKeyFile.exists()) {
- return keyMap;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- keyMap.put(key, connectionTime);
- }
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
- }
- return keyMap;
}
- /**
- * Returns the map of trusted networks from the keystore file.
- *
- * This was implemented in keystore version 1.
- */
- private List<String> getTrustedNetworks() {
- List<String> trustedNetworks = new ArrayList<String>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return trustedNetworks;
- }
+ private void addAdbKeyToKeyMap(TypedXmlPullParser parser) {
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ try {
+ long connectionTime =
+ parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION);
+ mKeyMap.put(key, connectionTime);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Error reading adbKey attributes", e);
}
- if (!mAtomicKeyFile.exists()) {
- return trustedNetworks;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return trustedNetworks;
- }
- int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
- if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
- Slog.e(TAG, "Keystore version=" + keystoreVersion
- + " not supported (max_supported="
- + MAX_SUPPORTED_KEYSTORE_VERSION);
- return trustedNetworks;
- }
- }
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
- trustedNetworks.add(bssid);
- }
- } catch (IOException | XmlPullParserException | NumberFormatException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
- }
- return trustedNetworks;
+ }
+
+ private void addTrustedNetworkToTrustedNetworks(TypedXmlPullParser parser) {
+ String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
+ mTrustedNetworks.add(bssid);
}
/**
* Updates the keystore with keys that were previously set to be always allowed before the
* connection time of keys was tracked.
*/
- private void addUserKeysToKeyStore() {
- File userKeyFile = getUserKeyFile();
+ private void addExistingUserKeysToKeyStore() {
+ if (mUserKeyFile == null || !mUserKeyFile.exists()) {
+ return;
+ }
boolean mapUpdated = false;
- if (userKeyFile != null && userKeyFile.exists()) {
- try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) {
- long time = System.currentTimeMillis();
- String key;
- while ((key = in.readLine()) != null) {
- // if the keystore does not contain the key from the user key file then add
- // it to the Map with the current system time to prevent it from expiring
- // immediately if the user is actively using this key.
- if (!mKeyMap.containsKey(key)) {
- mKeyMap.put(key, time);
- mapUpdated = true;
- }
+ try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) {
+ String key;
+ while ((key = in.readLine()) != null) {
+ // if the keystore does not contain the key from the user key file then add
+ // it to the Map with the current system time to prevent it from expiring
+ // immediately if the user is actively using this key.
+ if (!mKeyMap.containsKey(key)) {
+ mKeyMap.put(key, mTicker.currentTimeMillis());
+ mapUpdated = true;
}
- } catch (IOException e) {
- Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e);
}
if (mapUpdated) {
sendPersistKeyStoreMessage();
@@ -2147,7 +2053,9 @@
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for writing");
return;
}
}
@@ -2178,17 +2086,21 @@
Slog.e(TAG, "Caught an exception writing the key map: ", e);
mAtomicKeyFile.failWrite(keyStream);
}
+ writeKeys(mKeyMap.keySet());
}
private boolean filterOutOldKeys() {
- boolean keysDeleted = false;
long allowedTime = getAllowedConnectionTime();
- long systemTime = System.currentTimeMillis();
+ if (allowedTime == 0) {
+ return false;
+ }
+ boolean keysDeleted = false;
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
long connectionTime = keyEntry.getValue();
- if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) {
+ if (systemTime > (connectionTime + allowedTime)) {
keyMapIterator.remove();
keysDeleted = true;
}
@@ -2212,7 +2124,7 @@
if (allowedTime == 0) {
return minExpiration;
}
- long systemTime = System.currentTimeMillis();
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
@@ -2233,7 +2145,9 @@
public void deleteKeyStore() {
mKeyMap.clear();
mTrustedNetworks.clear();
- deleteKeyFile();
+ if (mUserKeyFile != null) {
+ mUserKeyFile.delete();
+ }
if (mAtomicKeyFile == null) {
return;
}
@@ -2260,7 +2174,8 @@
* is set to true the time will be set even if it is older than the previously written
* connection time.
*/
- public void setLastConnectionTime(String key, long connectionTime, boolean force) {
+ @VisibleForTesting
+ void setLastConnectionTime(String key, long connectionTime, boolean force) {
// Do not set the connection time to a value that is earlier than what was previously
// stored as the last connection time unless force is set.
if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) {
@@ -2271,11 +2186,6 @@
if (mSystemKeys.contains(key)) {
return;
}
- // if this is the first time the key is being added then write it to the key file as
- // well.
- if (!mKeyMap.containsKey(key)) {
- writeKey(key);
- }
mKeyMap.put(key, connectionTime);
}
@@ -2307,12 +2217,8 @@
long allowedConnectionTime = getAllowedConnectionTime();
// if the allowed connection time is 0 then revert to the previous behavior of always
// allowing previously granted adb grants.
- if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
- + allowedConnectionTime))) {
- return true;
- } else {
- return false;
- }
+ return allowedConnectionTime == 0
+ || (mTicker.currentTimeMillis() < (lastConnectionTime + allowedConnectionTime));
}
/**
@@ -2324,4 +2230,15 @@
return mTrustedNetworks.contains(bssid);
}
}
+
+ /**
+ * A Guava-like interface for getting the current system time.
+ *
+ * This allows us to swap a fake ticker in for testing to reduce "Thread.sleep()" calls and test
+ * for exact expected times instead of random ones.
+ */
+ @VisibleForTesting
+ interface Ticker {
+ long currentTimeMillis();
+ }
}
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 5d0c732..55d8dba 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -152,6 +152,14 @@
}
@Override
+ public void notifyKeyFilesUpdated() {
+ if (mDebuggingManager == null) {
+ return;
+ }
+ mDebuggingManager.notifyKeyFilesUpdated();
+ }
+
+ @Override
public void startAdbdForTransport(byte transportType) {
FgThread.getHandler().sendMessage(obtainMessage(
AdbService::setAdbdEnabledForTransport, AdbService.this, true, transportType));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 43d77ab..0040ea9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -365,6 +365,8 @@
private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
+ private static final int MSG_ROTATION_UPDATE = 48;
+ private static final int MSG_FOLD_UPDATE = 49;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1251,7 +1253,9 @@
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (mMonitorRotation) {
- RotationHelper.init(mContext, mAudioHandler);
+ RotationHelper.init(mContext, mAudioHandler,
+ rotationParam -> onRotationUpdate(rotationParam),
+ foldParam -> onFoldUpdate(foldParam));
}
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1398,6 +1402,20 @@
}
//-----------------------------------------------------------------
+ // rotation/fold updates coming from RotationHelper
+ void onRotationUpdate(String rotationParameter) {
+ // use REPLACE as only the last rotation matters
+ sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ rotationParameter, /*delay*/ 0);
+ }
+
+ void onFoldUpdate(String foldParameter) {
+ // use REPLACE as only the last fold state matters
+ sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ foldParameter, /*delay*/ 0);
+ }
+
+ //-----------------------------------------------------------------
// monitoring requests for volume range initialization
@Override // AudioSystemAdapter.OnVolRangeInitRequestListener
public void onVolumeRangeInitRequestFromNative() {
@@ -8327,6 +8345,16 @@
case MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR:
dispatchDeviceVolumeBehavior((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
+
+ case MSG_ROTATION_UPDATE:
+ // rotation parameter format: "rotation=x" where x is one of 0, 90, 180, 270
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
+
+ case MSG_FOLD_UPDATE:
+ // fold parameter format: "device_folded=x" where x is one of on, off
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index eb8387f..5cdf58b 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -21,13 +21,14 @@
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
-import android.media.AudioSystem;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
+import java.util.function.Consumer;
+
/**
* Class to handle device rotation events for AudioService, and forward device rotation
* and folded state to the audio HALs through AudioSystem.
@@ -53,6 +54,10 @@
private static AudioDisplayListener sDisplayListener;
private static FoldStateListener sFoldStateListener;
+ /** callback to send rotation updates to AudioSystem */
+ private static Consumer<String> sRotationUpdateCb;
+ /** callback to send folded state updates to AudioSystem */
+ private static Consumer<String> sFoldUpdateCb;
private static final Object sRotationLock = new Object();
private static final Object sFoldStateLock = new Object();
@@ -67,13 +72,16 @@
* - sDisplayListener != null
* - sContext != null
*/
- static void init(Context context, Handler handler) {
+ static void init(Context context, Handler handler,
+ Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
if (context == null) {
throw new IllegalArgumentException("Invalid null context");
}
sContext = context;
sHandler = handler;
sDisplayListener = new AudioDisplayListener();
+ sRotationUpdateCb = rotationUpdateCb;
+ sFoldUpdateCb = foldUpdateCb;
enable();
}
@@ -115,21 +123,26 @@
if (DEBUG_ROTATION) {
Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
}
+ String rotationParam;
switch (rotation) {
case Surface.ROTATION_0:
- AudioSystem.setParameters("rotation=0");
+ rotationParam = "rotation=0";
break;
case Surface.ROTATION_90:
- AudioSystem.setParameters("rotation=90");
+ rotationParam = "rotation=90";
break;
case Surface.ROTATION_180:
- AudioSystem.setParameters("rotation=180");
+ rotationParam = "rotation=180";
break;
case Surface.ROTATION_270:
- AudioSystem.setParameters("rotation=270");
+ rotationParam = "rotation=270";
break;
default:
Log.e(TAG, "Unknown device rotation");
+ rotationParam = null;
+ }
+ if (rotationParam != null) {
+ sRotationUpdateCb.accept(rotationParam);
}
}
@@ -140,11 +153,13 @@
synchronized (sFoldStateLock) {
if (sDeviceFold != newFolded) {
sDeviceFold = newFolded;
+ String foldParam;
if (newFolded) {
- AudioSystem.setParameters("device_folded=on");
+ foldParam = "device_folded=on";
} else {
- AudioSystem.setParameters("device_folded=off");
+ foldParam = "device_folded=off";
}
+ sFoldUpdateCb.accept(foldParam);
}
}
}
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index b6a4135..452bdf4 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -189,6 +189,7 @@
if (adbManager.getAdbTempKeysFile() != null) {
writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
}
+ adbManager.notifyKeyFilesUpdated();
}
private void configureUser() {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3c0cac0..ae61f24 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -149,9 +149,6 @@
final @TransitionType int mType;
private int mSyncId = -1;
- // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to
- // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging.
- private int mDebugId = -1;
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
@@ -295,11 +292,6 @@
return mSyncId;
}
- @VisibleForTesting
- int getDebugId() {
- return mDebugId;
- }
-
@TransitionFlags
int getFlags() {
return mFlags;
@@ -315,6 +307,10 @@
return mFinishTransaction;
}
+ private boolean isCollecting() {
+ return mState == STATE_COLLECTING || mState == STATE_STARTED;
+ }
+
/** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
void startCollecting(long timeoutMs) {
if (mState != STATE_PENDING) {
@@ -322,7 +318,6 @@
}
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
- mDebugId = mSyncId;
mController.mTransitionTracer.logState(this);
}
@@ -353,7 +348,10 @@
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Transition hasn't started collecting.");
}
- if (mSyncId < 0) return;
+ if (!isCollecting()) {
+ // Too late, transition already started playing, so don't collect.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -403,7 +401,10 @@
* or waiting until after the animation to close).
*/
void collectExistenceChange(@NonNull WindowContainer wc) {
- if (mSyncId < 0) return;
+ if (mState >= STATE_PLAYING) {
+ // Too late to collect. Don't check too-early here since `collect` will check that.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
+ " %s", mSyncId, wc);
collect(wc);
@@ -437,7 +438,7 @@
*/
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
- if (mSyncId < 0) return;
+ if (!isCollecting()) return;
mOverrideOptions = options;
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
@@ -455,7 +456,7 @@
* The transition will wait for all groups to be ready.
*/
void setReady(WindowContainer wc, boolean ready) {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setReadyFrom(wc, ready);
applyReady();
}
@@ -473,7 +474,7 @@
* @see ReadyTracker#setAllReady.
*/
void setAllReady() {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setAllReady();
applyReady();
}
@@ -672,7 +673,7 @@
SurfaceControl.Transaction inputSinkTransaction = null;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || !ar.isVisible()) continue;
+ if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
if (inputSinkTransaction == null) {
inputSinkTransaction = new SurfaceControl.Transaction();
}
@@ -889,7 +890,6 @@
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
- mSyncId = -1;
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1614,7 +1614,7 @@
}
boolean getLegacyIsReady() {
- return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+ return isCollecting() && mSyncId >= 0;
}
static Transition fromBinder(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index b1951e0..c1927d8 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -79,7 +79,7 @@
final ProtoOutputStream outputStream = new ProtoOutputStream();
final long transitionEntryToken = outputStream.start(TRANSITION);
- outputStream.write(ID, transition.getDebugId());
+ outputStream.write(ID, transition.getSyncId());
outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
outputStream.write(TRANSITION_TYPE, transition.mType);
outputStream.write(STATE, transition.getState());
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index b36aa06..e87dd4b 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -36,8 +36,6 @@
import androidx.test.InstrumentationRegistry;
-import com.android.server.FgThread;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +46,11 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
@@ -88,6 +91,7 @@
private long mOriginalAllowedConnectionTime;
private File mAdbKeyXmlFile;
private File mAdbKeyFile;
+ private FakeTicker mFakeTicker;
@Before
public void setUp() throws Exception {
@@ -96,14 +100,25 @@
if (mAdbKeyFile.exists()) {
mAdbKeyFile.delete();
}
- mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile);
mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
if (mAdbKeyXmlFile.exists()) {
mAdbKeyXmlFile.delete();
}
+
+ mFakeTicker = new FakeTicker();
+ // Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released)
+ mFakeTicker.advance(1224658800L);
+
mThread = new AdbDebuggingThreadTest();
- mKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+ mManager = new AdbDebuggingManager(
+ mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker);
+
+ mHandler = mManager.mHandler;
+ mThread.setHandler(mHandler);
+
+ mHandler.initKeyStore();
+ mKeyStore = mHandler.mAdbKeyStore;
+
mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
mBlockingQueue = new ArrayBlockingQueue<>(1);
}
@@ -122,7 +137,7 @@
private void setAllowedConnectionTime(long connectionTime) {
Settings.Global.putLong(mContext.getContentResolver(),
Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
- };
+ }
@Test
public void testAllowNewKeyOnce() throws Exception {
@@ -158,20 +173,15 @@
// Allow a connection from a new key with the 'Always allow' option selected.
runAdbTest(TEST_KEY_1, true, true, false);
- // Get the last connection time for the currently connected key to verify that it is updated
- // after the disconnect.
- long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
-
- // Sleep for a small amount of time to ensure a difference can be observed in the last
- // connection time after a disconnect.
- Thread.sleep(10);
+ // Advance the clock by 10ms to ensure there's a difference
+ mFakeTicker.advance(10 * 1_000_000);
// Send the disconnect message for the currently connected key to trigger an update of the
// last connection time.
disconnectKey(TEST_KEY_1);
- assertNotEquals(
+ assertEquals(
"The last connection time was not updated after the disconnect",
- lastConnectionTime,
+ mFakeTicker.currentTimeMillis(),
mKeyStore.getLastConnectionTime(TEST_KEY_1));
}
@@ -244,8 +254,8 @@
// Get the current last connection time for comparison after the scheduled job is run
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure that the updated connection time changes
- Thread.sleep(10);
+ // Advance a small amount of time to ensure that the updated connection time changes
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key
updateKeyStore();
@@ -269,13 +279,13 @@
persistKeyStore();
assertTrue(
"The key with the 'Always allow' option selected was not persisted in the keystore",
- mManager.new AdbKeyStore(mAdbKeyXmlFile).isKeyAuthorized(TEST_KEY_1));
+ mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1));
// Get the current last connection time to ensure it is updated in the persisted keystore.
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure the last connection time is updated.
- Thread.sleep(10);
+ // Advance a small amount of time to ensure the last connection time is updated.
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key.
updateKeyStore();
@@ -286,7 +296,7 @@
assertNotEquals(
"The last connection time in the key file was not updated after the update "
+ "connection time message", lastConnectionTime,
- mManager.new AdbKeyStore(mAdbKeyXmlFile).getLastConnectionTime(TEST_KEY_1));
+ mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1));
// Verify that the key is in the adb_keys file
assertTrue("The key was not in the adb_keys file after persisting the keystore",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
@@ -327,8 +337,8 @@
// Set the allowed window to a small value to ensure the time is beyond the allowed window.
setAllowedConnectionTime(1);
- // Sleep for a small amount of time to exceed the allowed window.
- Thread.sleep(10);
+ // Advance a small amount of time to exceed the allowed window.
+ mFakeTicker.advance(10);
// The AdbKeyStore has a method to get the time of the next key expiration to ensure the
// scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs
@@ -478,9 +488,12 @@
// Set the current expiration time to a minute from expiration and verify this new value is
// returned.
final long newExpirationTime = 60000;
- mKeyStore.setLastConnectionTime(TEST_KEY_1,
- System.currentTimeMillis() - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
- + newExpirationTime, true);
+ mKeyStore.setLastConnectionTime(
+ TEST_KEY_1,
+ mFakeTicker.currentTimeMillis()
+ - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
+ + newExpirationTime,
+ true);
expirationTime = mKeyStore.getNextExpirationTime();
if (Math.abs(expirationTime - newExpirationTime) > epsilon) {
fail("The expiration time for a key about to expire, " + expirationTime
@@ -525,7 +538,7 @@
// Get the last connection time for the key to verify that it is updated when the connected
// key message is sent.
long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY,
TEST_KEY_1).sendToTarget();
flushHandlerQueue();
@@ -536,7 +549,7 @@
// Verify that the scheduled job updates the connection time of the key.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for the key must be updated when the update keystore message"
@@ -545,7 +558,7 @@
// Verify that the connection time is updated when the key is disconnected.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
disconnectKey(TEST_KEY_1);
assertNotEquals(
"The connection time for the key must be updated when the disconnected message is"
@@ -628,11 +641,11 @@
setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
// The untracked keys should be added to the keystore as part of the constructor.
- AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
+ AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore();
// Verify that the connection time for each test key is within a small value of the current
// time.
- long time = System.currentTimeMillis();
+ long time = mFakeTicker.currentTimeMillis();
for (String key : testKeys) {
long connectionTime = adbKeyStore.getLastConnectionTime(key);
if (Math.abs(time - connectionTime) > epsilon) {
@@ -651,11 +664,11 @@
runAdbTest(TEST_KEY_1, true, true, false);
runAdbTest(TEST_KEY_2, true, true, false);
- // Sleep a small amount of time to ensure the connection time is updated by the scheduled
+ // Advance a small amount of time to ensure the connection time is updated by the scheduled
// job.
long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after the scheduled job runs",
@@ -669,7 +682,7 @@
disconnectKey(TEST_KEY_2);
connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after another key is "
@@ -686,8 +699,6 @@
// to clear the adb authorizations when adb is disabled after a boot a NullPointerException
// was thrown as deleteKeyStore is invoked against the key store. This test ensures the
// key store can be successfully cleared when adb is disabled.
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
-
clearKeyStore();
}
@@ -723,6 +734,9 @@
// Now remove one of the keys and make sure the other key is still there
mKeyStore.removeKey(TEST_KEY_1);
+ // Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE
+ flushHandlerQueue();
+
assertFalse("The key was still in the adb_keys file after removing the key",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
assertTrue("The key was not in the adb_keys file after removing a different key",
@@ -730,6 +744,95 @@
}
@Test
+ public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception {
+ setAllowedConnectionTime(0);
+
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size());
+ }
+
+ @Test
+ public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception {
+ long insertTime = mFakeTicker.currentTimeMillis();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ mFakeTicker.advance(10);
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertEquals(
+ "KeyStore not populated from the XML file.",
+ insertTime,
+ newKeyStore.getLastConnectionTime(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()
+ throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertFalse(
+ "Key is authorized after reloading deleted key files. Was state preserved?",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ // Back up the existing key files
+ Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp");
+ Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp");
+ Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING);
+
+ // Delete the existing key files
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ // Notify the manager that adb key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ // Copy the files back
+ Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ // Tell the manager that the key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertTrue(
+ "Key is not authorized after reloading key files.",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception {
+ String trustedNetwork = "My Network";
+ mKeyStore.addTrustedNetwork(trustedNetwork);
+ persistKeyStore();
+
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertTrue(
+ "Persisted trusted network not found in new keystore instance.",
+ newKeyStore.isTrustedNetwork(trustedNetwork));
+ }
+
+ @Test
public void testIsValidMdnsServiceName() {
// Longer than 15 characters
assertFalse(isValidMdnsServiceName("abcd1234abcd1234"));
@@ -1030,28 +1133,27 @@
if (key == null) {
return false;
}
+ return adbKeyFileKeys(keyFile).contains(key);
+ }
+
+ private static List<String> adbKeyFileKeys(File keyFile) throws Exception {
+ List<String> keys = new ArrayList<>();
if (keyFile.exists()) {
try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
String currKey;
while ((currKey = in.readLine()) != null) {
- if (key.equals(currKey)) {
- return true;
- }
+ keys.add(currKey);
}
}
}
- return false;
+ return keys;
}
/**
* Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
* indicating whether the key should be allowed to connect.
*/
- class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
- AdbDebuggingThreadTest() {
- mManager.super();
- }
-
+ private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
@Override
public void sendResponse(String msg) {
TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
@@ -1091,4 +1193,17 @@
return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
}
}
+
+ private static class FakeTicker implements AdbDebuggingManager.Ticker {
+ private long mCurrentTime;
+
+ private void advance(long milliseconds) {
+ mCurrentTime += milliseconds;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTime;
+ }
+ }
}