From 40f4f799bb14ebf416d2d421f75e95c0b68bc9df Mon Sep 17 00:00:00 2001 From: irisykyang Date: Tue, 10 Apr 2018 15:34:33 +0800 Subject: [PATCH 1/6] Reload APNs setting page when receive the intent ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED Bug: 77250264 Test: Manual testing Change-Id: Ie28c67c89a2590425ff9c06153b53dac6cf141f0 Merged-In: Ie28c67c89a2590425ff9c06153b53dac6cf141f0 --- .../android/settings/network/ApnSettings.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/network/ApnSettings.java b/src/com/android/settings/network/ApnSettings.java index 0537d8fe160..ff4e8fb4125 100755 --- a/src/com/android/settings/network/ApnSettings.java +++ b/src/com/android/settings/network/ApnSettings.java @@ -110,7 +110,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements private String mSelectedKey; - private IntentFilter mMobileStateFilter; + private IntentFilter mIntentFilter; private boolean mUnavailable; @@ -121,7 +121,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); } - private final BroadcastReceiver mMobileStateReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals( @@ -136,6 +136,11 @@ public class ApnSettings extends RestrictedSettingsFragment implements } break; } + } else if(intent.getAction().equals( + TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { + if (!mRestoreDefaultApnMode) { + fillList(); + } } } }; @@ -161,8 +166,9 @@ public class ApnSettings extends RestrictedSettingsFragment implements final int subId = activity.getIntent().getIntExtra(SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mMobileStateFilter = new IntentFilter( + mIntentFilter = new IntentFilter( TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); + mIntentFilter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); setIfOnlyAvailableForAdmins(true); @@ -209,7 +215,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements return; } - getActivity().registerReceiver(mMobileStateReceiver, mMobileStateFilter); + getActivity().registerReceiver(mReceiver, mIntentFilter); if (!mRestoreDefaultApnMode) { fillList(); @@ -224,7 +230,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements return; } - getActivity().unregisterReceiver(mMobileStateReceiver); + getActivity().unregisterReceiver(mReceiver); } @Override From fd0c472bdcc135d0965497f20f9d0ef5d1f9b49d Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 7 Aug 2018 15:12:54 -0700 Subject: [PATCH 2/6] Disable bg restriction for "Disallow apps control" Use RestrictedPreference so it could handle it automatically Change-Id: Ibf58934639677b88316366b2e00790f9556d0966 Fixes: 64474641 Test: Robotests --- res/xml/power_usage_detail.xml | 9 +++-- ...ackgroundActivityPreferenceController.java | 6 ++++ ...roundActivityPreferenceControllerTest.java | 35 ++++++++++++------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/res/xml/power_usage_detail.xml b/res/xml/power_usage_detail.xml index 0493a51799c..1891702b6eb 100644 --- a/res/xml/power_usage_detail.xml +++ b/res/xml/power_usage_detail.xml @@ -15,7 +15,9 @@ limitations under the License. --> - + - + android:selectable="true" + settings:userRestriction="no_control_apps"/> Date: Thu, 9 Aug 2018 16:31:09 -0700 Subject: [PATCH 3/6] 7/n: Add enrollment animation Fixes: 112005540 Test: Tested with ag/4749121 Change-Id: I7d51187f7b8b7a6c2c34c984740b76bc9fd89262 --- res/values/colors.xml | 6 + .../biometrics/BiometricEnrollSidecar.java | 6 +- .../biometrics/face/AnimationParticle.java | 236 ++++++++++++++++++ .../face/FaceEnrollAnimationDrawable.java | 70 +++++- .../biometrics/face/FaceEnrollEnrolling.java | 26 +- .../face/FaceEnrollPreviewFragment.java | 35 ++- .../biometrics/face/ParticleCollection.java | 143 +++++++++++ .../FingerprintEnrollEnrolling.java | 2 +- .../FingerprintEnrollFindSensor.java | 2 +- 9 files changed, 506 insertions(+), 20 deletions(-) create mode 100644 src/com/android/settings/biometrics/face/AnimationParticle.java create mode 100644 src/com/android/settings/biometrics/face/ParticleCollection.java diff --git a/res/values/colors.xml b/res/values/colors.xml index e5f7c276ae9..f398d9225ce 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -136,5 +136,11 @@ #fdd835 #f44336 + + #ff00bcd4 + #ffef6c00 + #ff4caf50 + #fffdd835 + #ff9e9e9e diff --git a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java index 111fecd881e..cedbec1a0ac 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollSidecar.java +++ b/src/com/android/settings/biometrics/BiometricEnrollSidecar.java @@ -37,7 +37,7 @@ import java.util.ArrayList; public abstract class BiometricEnrollSidecar extends InstrumentedFragment { public interface Listener { - void onEnrollmentHelp(CharSequence helpString); + void onEnrollmentHelp(int helpMsgId, CharSequence helpString); void onEnrollmentError(int errMsgId, CharSequence errString); void onEnrollmentProgressChange(int steps, int remaining); } @@ -82,7 +82,7 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { @Override public void send(Listener listener) { - listener.onEnrollmentHelp(helpString); + listener.onEnrollmentHelp(helpMsgId, helpString); } } @@ -174,7 +174,7 @@ public abstract class BiometricEnrollSidecar extends InstrumentedFragment { protected void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (mListener != null) { - mListener.onEnrollmentHelp(helpString); + mListener.onEnrollmentHelp(helpMsgId, helpString); } else { mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString)); } diff --git a/src/com/android/settings/biometrics/face/AnimationParticle.java b/src/com/android/settings/biometrics/face/AnimationParticle.java new file mode 100644 index 00000000000..a192e9f1428 --- /dev/null +++ b/src/com/android/settings/biometrics/face/AnimationParticle.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 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.settings.biometrics.face; + +import android.animation.ArgbEvaluator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.Log; + +import com.android.settings.R; + +import java.util.List; + +/** + * Class containing the state for an individual feedback dot / path. The dots are assigned colors + * based on their index. + */ +public class AnimationParticle { + + private static final String TAG = "AnimationParticle"; + + private static final int MIN_STROKE_WIDTH = 10; + private static final int MAX_STROKE_WIDTH = 20; // Be careful that this doesn't get clipped + private static final int FINAL_RING_STROKE_WIDTH = 15; + + private static final float ROTATION_SPEED_NORMAL = 0.8f; // radians per second, 1 = ~57 degrees + private static final float ROTATION_ACCELERATION_SPEED = 2.0f; + private static final float PULSE_SPEED_NORMAL = 1 * 2 * (float) Math.PI; // 1 cycle per second + private static final float RING_SWEEP_GROW_RATE_PRIMARY = 480; // degrees per second + private static final float RING_SWEEP_GROW_RATE_SECONDARY = 240; // degrees per second + private static final float RING_SIZE_FINALIZATION_TIME = 0.1f; // seconds + + private final Rect mBounds; // bounds for the canvas + private final int mBorderWidth; // amount of padding from the edges + private final ArgbEvaluator mEvaluator; + private final int mErrorColor; + private final int mIndex; + private final Listener mListener; + + private final Paint mPaint; + private final int mAssignedColor; + private final float mOffsetTimeSec; // stagger particle size to make a wave effect + + private int mLastAnimationState; + private int mAnimationState; + private float mCurrentSize = MIN_STROKE_WIDTH; + private float mCurrentAngle; // 0 is to the right, in radians + private float mRotationSpeed = ROTATION_SPEED_NORMAL; // speed of dot rotation + private float mSweepAngle = 0; // ring sweep, degrees per second + private float mSweepRate = RING_SWEEP_GROW_RATE_SECONDARY; // acceleration + private float mRingAdjustRate; // rate at which ring should grow/shrink to final size + private float mRingCompletionTime; // time at which ring should be completed + + public interface Listener { + void onRingCompleted(int index); + } + + public AnimationParticle(Context context, Listener listener, Rect bounds, int borderWidth, + int index, int totalParticles, List colors) { + mBounds = bounds; + mBorderWidth = borderWidth; + mEvaluator = new ArgbEvaluator(); + mErrorColor = context.getResources() + .getColor(R.color.face_anim_particle_error, context.getTheme()); + mIndex = index; + mListener = listener; + + mCurrentAngle = (float) index / totalParticles * 2 * (float) Math.PI; + mOffsetTimeSec = (float) index / totalParticles + * (1 / ROTATION_SPEED_NORMAL) * 2 * (float) Math.PI; + + mPaint = new Paint(); + mAssignedColor = colors.get(index % colors.size()); + mPaint.setColor(mAssignedColor); + mPaint.setAntiAlias(true); + mPaint.setStrokeWidth(mCurrentSize); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeCap(Paint.Cap.ROUND); + } + + public void updateState(int animationState) { + if (mAnimationState == animationState) { + Log.w(TAG, "Already in state " + animationState); + return; + } + if (animationState == ParticleCollection.STATE_COMPLETE) { + mPaint.setStyle(Paint.Style.STROKE); + } + mLastAnimationState = mAnimationState; + mAnimationState = animationState; + } + + // There are two types of particles, secondary and primary. Primary particles accelerate faster + // during the "completed" animation. Particles are secondary by default. + public void setAsPrimary() { + mSweepRate = RING_SWEEP_GROW_RATE_PRIMARY; + } + + public void update(long t, long dt) { + if (mAnimationState != ParticleCollection.STATE_COMPLETE) { + updateDot(t, dt); + } else { + updateRing(t, dt); + } + } + + private void updateDot(long t, long dt) { + final float dtSec = 0.001f * dt; + final float tSec = 0.001f * t; + + final float multiplier = mRotationSpeed / ROTATION_SPEED_NORMAL; + + // Calculate rotation speed / angle + if ((mAnimationState == ParticleCollection.STATE_STOPPED_COLORFUL + || mAnimationState == ParticleCollection.STATE_STOPPED_GRAY) + && mRotationSpeed > 0) { + // Linear slow down for now + mRotationSpeed = Math.max(mRotationSpeed - ROTATION_ACCELERATION_SPEED * dtSec, 0); + } else if (mAnimationState == ParticleCollection.STATE_STARTED + && mRotationSpeed < ROTATION_SPEED_NORMAL) { + // Linear speed up for now + mRotationSpeed += ROTATION_ACCELERATION_SPEED * dtSec; + } + + mCurrentAngle += dtSec * mRotationSpeed; + + // Calculate dot / ring size; linearly proportional with rotation speed + mCurrentSize = + (MAX_STROKE_WIDTH - MIN_STROKE_WIDTH) / 2 + * (float) Math.sin(tSec * PULSE_SPEED_NORMAL + mOffsetTimeSec) + + (MAX_STROKE_WIDTH + MIN_STROKE_WIDTH) / 2; + mCurrentSize = (mCurrentSize - MIN_STROKE_WIDTH) * multiplier + MIN_STROKE_WIDTH; + + // Calculate paint color; linearly proportional to rotation speed + int color = mAssignedColor; + if (mAnimationState == ParticleCollection.STATE_STOPPED_GRAY) { + color = (int) mEvaluator.evaluate(1 - multiplier, mAssignedColor, mErrorColor); + } else if (mLastAnimationState == ParticleCollection.STATE_STOPPED_GRAY) { + color = (int) mEvaluator.evaluate(1 - multiplier, mAssignedColor, mErrorColor); + } + + mPaint.setColor(color); + mPaint.setStrokeWidth(mCurrentSize); + } + + private void updateRing(long t, long dt) { + final float dtSec = 0.001f * dt; + final float tSec = 0.001f * t; + + // Store the start time, since we need to guarantee all rings reach final size at same time + // independent of current size. The magic 0 check is safe. + if (mRingAdjustRate == 0) { + mRingAdjustRate = + (FINAL_RING_STROKE_WIDTH - mCurrentSize) / RING_SIZE_FINALIZATION_TIME; + if (mRingCompletionTime == 0) { + mRingCompletionTime = tSec + RING_SIZE_FINALIZATION_TIME; + } + } + + // Accelerate to attack speed.. jk, back to normal speed + if (mRotationSpeed < ROTATION_SPEED_NORMAL) { + mRotationSpeed += ROTATION_ACCELERATION_SPEED * dtSec; + } + + // For arcs, this is the "start" + mCurrentAngle += dtSec * mRotationSpeed; + + // Update the sweep angle until it fills entire circle + if (mSweepAngle < 360) { + final float sweepGrowth = mSweepRate * dtSec; + mSweepAngle = mSweepAngle + sweepGrowth; + mSweepRate = mSweepRate + sweepGrowth; + } + if (mSweepAngle > 360) { + mSweepAngle = 360; + mListener.onRingCompleted(mIndex); + } + + // Animate stroke width to final size. + if (tSec < RING_SIZE_FINALIZATION_TIME) { + mCurrentSize = mCurrentSize + mRingAdjustRate * dtSec; + mPaint.setStrokeWidth(mCurrentSize); + } else { + // There should be small to no discontinuity in this if/else + mCurrentSize = FINAL_RING_STROKE_WIDTH; + mPaint.setStrokeWidth(mCurrentSize); + } + + } + + public void draw(Canvas canvas) { + if (mAnimationState != ParticleCollection.STATE_COMPLETE) { + drawDot(canvas); + } else { + drawRing(canvas); + } + } + + // Draws a dot at the current position on the circumference of the path. + private void drawDot(Canvas canvas) { + final float w = mBounds.right - mBounds.exactCenterX() - mBorderWidth; + final float h = mBounds.bottom - mBounds.exactCenterY() - mBorderWidth; + canvas.drawCircle( + mBounds.exactCenterX() + w * (float) Math.cos(mCurrentAngle), + mBounds.exactCenterY() + h * (float) Math.sin(mCurrentAngle), + mCurrentSize, + mPaint); + } + + private void drawRing(Canvas canvas) { + RectF arc = new RectF( + mBorderWidth, mBorderWidth, + mBounds.width() - mBorderWidth, mBounds.height() - mBorderWidth); + Path path = new Path(); + path.arcTo(arc, (float) Math.toDegrees(mCurrentAngle), mSweepAngle); + canvas.drawPath(path, mPaint); + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java index 0da666cc9f6..5be7c5331d3 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java @@ -16,6 +16,8 @@ package com.android.settings.biometrics.face; +import android.animation.TimeAnimator; +import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -26,16 +28,43 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; -/** - * A drawable containing the circle cutout. - */ -public class FaceEnrollAnimationDrawable extends Drawable { +import com.android.settings.biometrics.BiometricEnrollSidecar; +/** + * A drawable containing the circle cutout as well as the animations. + */ +public class FaceEnrollAnimationDrawable extends Drawable + implements BiometricEnrollSidecar.Listener { + + // Tune this parameter so the UI looks nice - and so that we don't have to draw the animations + // outside our bounds. A fraction of each rotating dot should be overlapping the camera preview. + private static final int BORDER_BOUNDS = 20; + + private final Context mContext; + private final ParticleCollection.Listener mListener; private Rect mBounds; private final Paint mSquarePaint; private final Paint mCircleCutoutPaint; - public FaceEnrollAnimationDrawable() { + private ParticleCollection mParticleCollection; + + private TimeAnimator mTimeAnimator; + + private final ParticleCollection.Listener mAnimationListener + = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + if (mTimeAnimator != null && mTimeAnimator.isStarted()) { + mTimeAnimator.end(); + mListener.onEnrolled(); + } + } + }; + + public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener listener) { + mContext = context; + mListener = listener; + mSquarePaint = new Paint(); mSquarePaint.setColor(Color.WHITE); mSquarePaint.setAntiAlias(true); @@ -46,9 +75,35 @@ public class FaceEnrollAnimationDrawable extends Drawable { mCircleCutoutPaint.setAntiAlias(true); } + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mParticleCollection.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mParticleCollection.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + mParticleCollection.onEnrollmentProgressChange(steps, remaining); + } + @Override protected void onBoundsChange(Rect bounds) { mBounds = bounds; + mParticleCollection = + new ParticleCollection(mContext, mAnimationListener, bounds, BORDER_BOUNDS); + + if (mTimeAnimator == null) { + mTimeAnimator = new TimeAnimator(); + mTimeAnimator.setTimeListener((animation, totalTimeMs, deltaTimeMs) -> { + mParticleCollection.update(totalTimeMs, deltaTimeMs); + FaceEnrollAnimationDrawable.this.invalidateSelf(); + }); + mTimeAnimator.start(); + } } @Override @@ -63,7 +118,10 @@ public class FaceEnrollAnimationDrawable extends Drawable { // Clear a circle in the middle for the camera preview canvas.drawCircle(mBounds.exactCenterX(), mBounds.exactCenterY(), - mBounds.height() / 2, mCircleCutoutPaint); + mBounds.height() / 2 - BORDER_BOUNDS, mCircleCutoutPaint); + + // Draw the animation + mParticleCollection.draw(canvas); canvas.restore(); } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java index 7fac9f6724f..fccb39a20ee 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java @@ -45,7 +45,14 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { private TextView mErrorText; private Interpolator mLinearOutSlowInInterpolator; private boolean mShouldFinishOnStop = true; - private FaceEnrollPreviewFragment mFaceCameraPreview; + private FaceEnrollPreviewFragment mPreviewFragment; + + private ParticleCollection.Listener mListener = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + FaceEnrollEnrolling.this.launchFinish(mToken); + } + }; public static class FaceErrorDialog extends BiometricErrorDialog { static FaceErrorDialog newInstance(CharSequence msg, int msgId) { @@ -87,7 +94,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { if (shouldLaunchConfirmLock()) { launchConfirmLock(R.string.security_settings_face_preference_title, - Utils.getFaceManagerOrNull(this).preEnroll()); + Utils.getFingerprintManagerOrNull(this).preEnroll()); mShouldFinishOnStop = false; } else { startEnrollment(); @@ -97,13 +104,14 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { @Override public void startEnrollment() { super.startEnrollment(); - mFaceCameraPreview = (FaceEnrollPreviewFragment) getSupportFragmentManager() + mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager() .findFragmentByTag(TAG_FACE_PREVIEW); - if (mFaceCameraPreview == null) { - mFaceCameraPreview = new FaceEnrollPreviewFragment(); - getSupportFragmentManager().beginTransaction().add(mFaceCameraPreview, TAG_FACE_PREVIEW) + if (mPreviewFragment == null) { + mPreviewFragment = new FaceEnrollPreviewFragment(); + getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW) .commitAllowingStateLoss(); } + mPreviewFragment.setListener(mListener); } @Override @@ -132,10 +140,11 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (!TextUtils.isEmpty(helpString)) { showError(helpString); } + mPreviewFragment.onEnrollmentHelp(helpMsgId, helpString); } @Override @@ -149,6 +158,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { msgId = R.string.security_settings_face_enroll_error_generic_dialog_message; break; } + mPreviewFragment.onEnrollmentError(errMsgId, errString); showErrorDialog(getText(msgId), errMsgId); } @@ -157,6 +167,8 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { if (DEBUG) { Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining); } + mPreviewFragment.onEnrollmentProgressChange(steps, remaining); + // TODO: Update the actual animation showError("Steps: " + steps + " Remaining: " + remaining); } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java index 8bb8b929f36..1861e10a585 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java @@ -38,6 +38,7 @@ import android.widget.ImageView; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.biometrics.BiometricEnrollSidecar; import com.android.settings.core.InstrumentedPreferenceFragment; import java.util.ArrayList; @@ -50,7 +51,8 @@ import java.util.List; * Fragment that contains the logic for showing and controlling the camera preview, circular * overlay, as well as the enrollment animations. */ -public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { +public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment + implements BiometricEnrollSidecar.Listener { private static final String TAG = "FaceEnrollPreviewFragment"; @@ -65,6 +67,7 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { private CameraCaptureSession mCaptureSession; private CaptureRequest mPreviewRequest; private Size mPreviewSize; + private ParticleCollection.Listener mListener; // View used to contain the circular cutout and enrollment animation drawable private ImageView mCircleView; @@ -75,6 +78,15 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { // Texture used for showing the camera preview private FaceSquareTextureView mTextureView; + // Listener sent to the animation drawable + private final ParticleCollection.Listener mAnimationListener + = new ParticleCollection.Listener() { + @Override + public void onEnrolled() { + mListener.onEnrolled(); + } + }; + private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @@ -185,7 +197,7 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { // Must disable hardware acceleration for this view, otherwise transparency breaks mCircleView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - mAnimationDrawable = new FaceEnrollAnimationDrawable(); + mAnimationDrawable = new FaceEnrollAnimationDrawable(getContext(), mAnimationListener); mCircleView.setImageDrawable(mAnimationDrawable); mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); @@ -212,6 +224,25 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { closeCamera(); } + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mAnimationDrawable.onEnrollmentError(errMsgId, errString); + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mAnimationDrawable.onEnrollmentHelp(helpMsgId, helpString); + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + mAnimationDrawable.onEnrollmentProgressChange(steps, remaining); + } + + public void setListener(ParticleCollection.Listener listener) { + mListener = listener; + } + /** * Sets up member variables related to camera. * diff --git a/src/com/android/settings/biometrics/face/ParticleCollection.java b/src/com/android/settings/biometrics/face/ParticleCollection.java new file mode 100644 index 00000000000..399beec3abd --- /dev/null +++ b/src/com/android/settings/biometrics/face/ParticleCollection.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 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.settings.biometrics.face; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Rect; + +import com.android.settings.R; +import com.android.settings.biometrics.BiometricEnrollSidecar; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Class that's used to create, maintain, and update the state of each animation particle. Particles + * should have their colors assigned based on their index. Particles are split into primary and + * secondary types - primary types animate twice as fast during the completion effect. The particles + * are updated/drawn in a special order so that the overlap is correct during the final completion + * effect. + */ +public class ParticleCollection implements BiometricEnrollSidecar.Listener { + + private static final String TAG = "AnimationController"; + + private static final int NUM_PARTICLES = 12; + + public static final int STATE_STARTED = 1; // dots are rotating + public static final int STATE_STOPPED_COLORFUL = 2; // dots are not rotating but colorful + public static final int STATE_STOPPED_GRAY = 3; // dots are not rotating and also gray (error) + public static final int STATE_COMPLETE = 4; // face is enrolled + + private final List mParticleList; + private final List mPrimariesInProgress; // primary particles not done animating yet + private int mState; + private Listener mListener; + + public interface Listener { + void onEnrolled(); + } + + private final AnimationParticle.Listener mParticleListener = new AnimationParticle.Listener() { + @Override + public void onRingCompleted(int index) { + final boolean wasEmpty = mPrimariesInProgress.isEmpty(); + // We can stop the time animator once the three primary particles have finished + for (int i = 0; i < mPrimariesInProgress.size(); i++) { + if (mPrimariesInProgress.get(i).intValue() == index) { + mPrimariesInProgress.remove(i); + break; + } + } + if (mPrimariesInProgress.isEmpty() && !wasEmpty) { + mListener.onEnrolled(); + } + } + }; + + public ParticleCollection(Context context, Listener listener, Rect bounds, int borderWidth) { + mParticleList = new ArrayList<>(); + mListener = listener; + + final List colors = new ArrayList<>(); + final Resources.Theme theme = context.getTheme(); + final Resources resources = context.getResources(); + colors.add(resources.getColor(R.color.face_anim_particle_color_1, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_2, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_3, theme)); + colors.add(resources.getColor(R.color.face_anim_particle_color_4, theme)); + + // Primary particles expand faster during the completion animation + mPrimariesInProgress = new ArrayList<>(Arrays.asList(0, 4, 8)); + + // Order in which to draw the particles. This is so the final "completion" animation has + // the correct behavior. + final int[] order = {3, 7, 11, 2, 6, 10, 1, 5, 9, 0, 4, 8}; + + for (int i = 0; i < NUM_PARTICLES; i++) { + AnimationParticle particle = new AnimationParticle(context, mParticleListener, bounds, + borderWidth, order[i], NUM_PARTICLES, colors); + if (mPrimariesInProgress.contains(order[i])) { + particle.setAsPrimary(); + } + mParticleList.add(particle); + } + + updateState(STATE_STARTED); + } + + public void update(long t, long dt) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).update(t, dt); + } + } + + public void draw(Canvas canvas) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).draw(canvas); + } + } + + private void updateState(int state) { + if (mState != state) { + for (int i = 0; i < mParticleList.size(); i++) { + mParticleList.get(i).updateState(state); + } + mState = state; + } + } + + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + + } + + @Override + public void onEnrollmentProgressChange(int steps, int remaining) { + if (remaining == 0) { + updateState(STATE_COMPLETE); + } + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 38ef2c1b7f4..e6f3b04ae67 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -245,7 +245,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { if (!TextUtils.isEmpty(helpString)) { mErrorText.removeCallbacks(mTouchAgainRunnable); showError(helpString); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 927b5eb905e..c104eb3de94 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -94,7 +94,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase { } @Override - public void onEnrollmentHelp(CharSequence helpString) { + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { } @Override From 7373ec042b98a3152bce8c627067f25541eb82b4 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 14 Aug 2018 13:25:47 -0700 Subject: [PATCH 4/6] Migrate all remaining condition cards to v2. Bug: 112485407 Test: robotests Change-Id: I3c304c308fa6f499b4b5e21d4c5735cde7b90220 --- AndroidManifest.xml | 9 +- .../settings/dashboard/DashboardSummary.java | 1 - .../AbnormalRingerConditionBase.java | 3 +- .../conditional/AirplaneModeCondition.java | 4 + .../conditional/BackgroundDataCondition.java | 1 + .../conditional/BatterySaverCondition.java | 4 + .../conditional/CellularDataCondition.java | 1 + .../homepage/conditional/DndCondition.java | 4 + .../conditional/HotspotCondition.java | 3 +- .../conditional/NightDisplayCondition.java | 4 + .../conditional/RingerMutedCondition.java | 1 + .../conditional/RingerVibrateCondition.java | 1 + .../conditional/WorkModeCondition.java | 1 + .../v2/AbnormalRingerConditionController.java | 76 +++++++++++ .../v2/AirplaneModeConditionCard.java | 62 +++++++++ .../v2/AirplaneModeConditionController.java | 86 +++++++++++++ .../v2/BackgroundDataConditionCard.java | 62 +++++++++ .../v2/BackgroundDataConditionController.java | 65 ++++++++++ .../v2/BatterySaverConditionCard.java | 62 +++++++++ .../v2/BatterySaverConditionController.java | 92 +++++++++++++ .../v2/CellularDataConditionCard.java | 62 +++++++++ .../v2/CellularDataConditionController.java | 97 ++++++++++++++ .../conditional/v2/ConditionManager.java | 19 +++ .../v2/DndConditionCardController.java | 2 +- .../conditional/v2/HotspotConditionCard.java | 72 +++++++++++ .../v2/HotspotConditionController.java | 121 ++++++++++++++++++ .../v2/NightDisplayConditionCard.java | 62 +++++++++ .../v2/NightDisplayConditionController.java | 72 +++++++++++ .../v2/RingerMutedConditionCard.java | 62 +++++++++ .../v2/RingerMutedConditionController.java | 55 ++++++++ .../v2/RingerVibrateConditionCard.java | 62 +++++++++ .../v2/RingerVibrateConditionController.java | 41 ++++++ .../conditional/v2/WorkModeConditionCard.java | 63 +++++++++ .../v2/WorkModeConditionController.java | 90 +++++++++++++ ...malRingerConditionControllerBaseTest.java} | 50 +++----- ...ackgroundDataConditionControllerTest.java} | 16 +-- .../BatterySaverConditionControllerTest.java | 87 +++++++++++++ .../v2/WorkModeConditionControllerTest.java | 57 +++++++++ .../WallpaperSuggestionActivityTest.java | 20 +-- 39 files changed, 1582 insertions(+), 70 deletions(-) create mode 100644 src/com/android/settings/homepage/conditional/v2/AbnormalRingerConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/AirplaneModeConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/AirplaneModeConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/BackgroundDataConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/BackgroundDataConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/BatterySaverConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/BatterySaverConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/CellularDataConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/CellularDataConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/HotspotConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/HotspotConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/NightDisplayConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/NightDisplayConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/RingerMutedConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/RingerMutedConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/RingerVibrateConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/RingerVibrateConditionController.java create mode 100644 src/com/android/settings/homepage/conditional/v2/WorkModeConditionCard.java create mode 100644 src/com/android/settings/homepage/conditional/v2/WorkModeConditionController.java rename tests/robotests/src/com/android/settings/homepage/conditional/{AbnormalRingerConditionBaseTest.java => v2/AbnormalRingerConditionControllerBaseTest.java} (61%) rename tests/robotests/src/com/android/settings/homepage/conditional/{BackgroundDataConditionTest.java => v2/BackgroundDataConditionControllerTest.java} (79%) create mode 100644 tests/robotests/src/com/android/settings/homepage/conditional/v2/BatterySaverConditionControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/homepage/conditional/v2/WorkModeConditionControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d4357318c8a..9a771ac9848 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1892,7 +1892,8 @@ android:name=".development.DevelopmentSettingsDisabledActivity" android:icon="@drawable/ic_settings_development" android:label="@string/development_settings_title" - android:theme="@android:style/Theme.NoDisplay"> + android:excludeFromRecents="true" + android:theme="@style/Transparent"> @@ -3069,10 +3070,12 @@ - profiles = mUm.getProfiles(UserHandle.myUserId()); + final int profilesCount = profiles.size(); + mUserHandle = null; + for (int i = 0; i < profilesCount; i++) { + UserInfo userInfo = profiles.get(i); + if (userInfo.isManagedProfile()) { + // We assume there's only one managed profile, otherwise UI needs to change. + mUserHandle = userInfo.getUserHandle(); + break; + } + } + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/AbnormalRingerConditionControllerBaseTest.java similarity index 61% rename from tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/v2/AbnormalRingerConditionControllerBaseTest.java index efc5ceab889..dc06710335e 100644 --- a/tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/AbnormalRingerConditionControllerBaseTest.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.android.settings.homepage.conditional; +package com.android.settings.homepage.conditional.v2; -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.media.AudioManager; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -35,7 +33,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @RunWith(SettingsRobolectricTestRunner.class) -public class AbnormalRingerConditionBaseTest { +public class AbnormalRingerConditionControllerBaseTest { @Mock private ConditionManager mConditionManager; @@ -47,53 +45,39 @@ public class AbnormalRingerConditionBaseTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - when(mConditionManager.getContext()).thenReturn(mContext); - mCondition = new TestCondition(mConditionManager); + + mCondition = new TestCondition(mContext, mConditionManager); } @Test - public void newInstance_shouldMonitorRingerStateChangeBroadcast() { + public void startMonitor_shouldMonitorRingerStateChangeBroadcast() { final Intent broadcast1 = new Intent("foo.bar.action"); final Intent broadcast2 = new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); + mCondition.startMonitoringStateChange(); + mContext.sendBroadcast(broadcast1); - assertThat(mCondition.mRefreshCalled).isFalse(); + verify(mConditionManager, never()).onConditionChanged(); mContext.sendBroadcast(broadcast2); - assertThat(mCondition.mRefreshCalled).isTrue(); + verify(mConditionManager).onConditionChanged(); } - private static class TestCondition extends AbnormalRingerConditionBase { - private boolean mRefreshCalled; + private static class TestCondition extends AbnormalRingerConditionController { - TestCondition(ConditionManager manager) { - super(manager); + public TestCondition(Context appContext, ConditionManager conditionManager) { + super(appContext, conditionManager); } - @Override - public void refreshState() { - mRefreshCalled = true; - } @Override - public int getMetricsConstant() { + public long getId() { return 0; } @Override - public Drawable getIcon() { - return null; + public boolean isDisplayable() { + return false; } - - @Override - public CharSequence getTitle() { - return null; - } - - @Override - public CharSequence getSummary() { - return null; - } - } } diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/BackgroundDataConditionControllerTest.java similarity index 79% rename from tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/v2/BackgroundDataConditionControllerTest.java index 289fa7c22ba..b12d786067a 100644 --- a/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/BackgroundDataConditionControllerTest.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.homepage.conditional; +package com.android.settings.homepage.conditional.v2; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; @@ -31,30 +30,25 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @RunWith(SettingsRobolectricTestRunner.class) -public class BackgroundDataConditionTest { - @Mock - private ConditionManager mConditionManager; - +public class BackgroundDataConditionControllerTest { private Context mContext; + private BackgroundDataConditionController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - when(mConditionManager.getContext()).thenReturn(mContext); + mController = new BackgroundDataConditionController(mContext); } @Test public void onPrimaryClick_shouldReturn2SummaryActivity() { final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); - BackgroundDataCondition backgroundDataCondition - = new BackgroundDataCondition(mConditionManager); - backgroundDataCondition.onPrimaryClick(); + mController.onPrimaryClick(mContext); verify(mContext).startActivity(argumentCaptor.capture()); Intent intent = argumentCaptor.getValue(); diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/BatterySaverConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/BatterySaverConditionControllerTest.java new file mode 100644 index 00000000000..8849e2d58f3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/BatterySaverConditionControllerTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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.settings.homepage.conditional.v2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.IntentFilter; +import android.os.PowerManager; + +import com.android.settings.fuelgauge.BatterySaverReceiver; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPowerManager; + +@RunWith(SettingsRobolectricTestRunner.class) +public class BatterySaverConditionControllerTest { + @Mock + private ConditionManager mConditionManager; + + private ShadowPowerManager mPowerManager; + private Context mContext; + private BatterySaverConditionController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mPowerManager = Shadows.shadowOf(mContext.getSystemService(PowerManager.class)); + mController = new BatterySaverConditionController(mContext, mConditionManager); + } + + @Test + public void startMonitor_shouldRegisterReceiver() { + mController.startMonitoringStateChange(); + + verify(mContext).registerReceiver(any(BatterySaverReceiver.class), any(IntentFilter.class)); + } + + @Test + public void stopMonitor_shouldUnregisterReceiver() { + mController.startMonitoringStateChange(); + mController.stopMonitoringStateChange(); + + verify(mContext).unregisterReceiver(any(BatterySaverReceiver.class)); + } + + @Test + public void isDisplayable_PowerSaverOn_true() { + mPowerManager.setIsPowerSaveMode(true); + + assertThat(mController.isDisplayable()).isTrue(); + } + + @Test + public void isDisplayable_PowerSaverOff_false() { + mPowerManager.setIsPowerSaveMode(false); + + assertThat(mController.isDisplayable()).isFalse(); + } + +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/WorkModeConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/WorkModeConditionControllerTest.java new file mode 100644 index 00000000000..04d58412883 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/WorkModeConditionControllerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 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.settings.homepage.conditional.v2; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; + +import com.android.settings.Settings; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class WorkModeConditionControllerTest { + + private Context mContext; + private WorkModeConditionController mController; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mController = new WorkModeConditionController(mContext); + } + + @Test + public void onPrimaryClick_shouldLaunchAccountsSetting() { + final ComponentName componentName = + new ComponentName(mContext, Settings.AccountDashboardActivity.class); + + mController.onPrimaryClick(mContext); + + verify(mContext).startActivity( + argThat(intent -> intent.getComponent().equals(componentName))); + } + +} diff --git a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java index b6b3d60748a..67d5dc60977 100644 --- a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java +++ b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java @@ -17,14 +17,13 @@ package com.android.settings.wallpaper; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.when; import android.app.WallpaperManager; import android.content.Context; -import android.content.Intent; import android.content.res.Resources; -import com.android.settings.SubSettings; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.After; @@ -35,14 +34,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; -import org.robolectric.shadows.ShadowActivity; -import org.robolectric.shadows.ShadowPackageManager; @RunWith(SettingsRobolectricTestRunner.class) public class WallpaperSuggestionActivityTest { @@ -65,20 +61,6 @@ public class WallpaperSuggestionActivityTest { ShadowWallpaperManager.reset(); } - @Test - public void launch_primarySuggestionActivityDoesNotExist_shouldFallback() { - ShadowPackageManager packageManager = - Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager()); - packageManager.removePackage("com.android.settings"); - - ShadowActivity activity = Shadows.shadowOf(mController.setup().get()); - final Intent intent = activity.getNextStartedActivity(); - - assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName()); - assertThat(intent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT); - assertThat(activity.isFinishing()).isTrue(); - } - @Test public void wallpaperServiceEnabled_no_shouldReturnTrue() { when(mContext.getResources()).thenReturn(mResources); From b1facd2001656b46cc6f223bd9f66f8f62d0f55d Mon Sep 17 00:00:00 2001 From: Jordan Liu Date: Tue, 14 Aug 2018 16:52:07 -0700 Subject: [PATCH 5/6] Refresh subscription info when subid changes We store the initial subId and check if it has changed so that we always have the updated subscription info in fillList(). Fixes: 111731641 Test: manually verify that swapping the SIM loads new APNs Change-Id: I3475915f2c380fb67b2e56436423d491e489d91e --- .../android/settings/network/ApnSettings.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/network/ApnSettings.java b/src/com/android/settings/network/ApnSettings.java index ff4e8fb4125..288cccbe2b0 100755 --- a/src/com/android/settings/network/ApnSettings.java +++ b/src/com/android/settings/network/ApnSettings.java @@ -104,6 +104,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements private RestoreApnProcessHandler mRestoreApnProcessHandler; private HandlerThread mRestoreDefaultApnThread; private SubscriptionInfo mSubscriptionInfo; + private int mSubId; private UiccController mUiccController; private String mMvnoType; private String mMvnoMatchData; @@ -139,6 +140,13 @@ public class ApnSettings extends RestrictedSettingsFragment implements } else if(intent.getAction().equals( TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { if (!mRestoreDefaultApnMode) { + int extraSubId = intent.getIntExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + if (extraSubId != mSubId) { + // subscription has changed + mSubId = extraSubId; + mSubscriptionInfo = getSubscriptionInfo(mSubId); + } fillList(); } } @@ -163,7 +171,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements public void onCreate(Bundle icicle) { super.onCreate(icicle); final Activity activity = getActivity(); - final int subId = activity.getIntent().getIntExtra(SUB_ID, + mSubId = activity.getIntent().getIntExtra(SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); mIntentFilter = new IntentFilter( @@ -172,7 +180,7 @@ public class ApnSettings extends RestrictedSettingsFragment implements setIfOnlyAvailableForAdmins(true); - mSubscriptionInfo = SubscriptionManager.from(activity).getActiveSubscriptionInfo(subId); + mSubscriptionInfo = getSubscriptionInfo(mSubId); mUiccController = UiccController.getInstance(); CarrierConfigManager configManager = (CarrierConfigManager) @@ -253,6 +261,10 @@ public class ApnSettings extends RestrictedSettingsFragment implements return null; } + private SubscriptionInfo getSubscriptionInfo(int subId) { + return SubscriptionManager.from(getActivity()).getActiveSubscriptionInfo(subId); + } + private void fillList() { final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() From f5aa59e99efab30f195662d0e7ce6471420a23e9 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 14 Aug 2018 17:58:44 -0700 Subject: [PATCH 6/6] Implement createPrefControllers for tether page So tether preferences could show up in search Change-Id: I234680aff878451df2a634cb58538bb2627d2eff Fixes: 112599941 Test: Robotests --- .../wifi/tether/WifiTetherSettings.java | 32 ++++++++++++------- .../testutils/shadow/ShadowWifiManager.java | 5 +++ .../wifi/tether/WifiTetherSettingsTest.java | 19 ++++++++--- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index 16908d62a1a..47f3ceda391 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -99,6 +99,11 @@ public class WifiTetherSettings extends RestrictedDashboardFragment super.onAttach(context); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mTetherChangeReceiver = new TetherChangeReceiver(); + + mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class); + mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class); + mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class); + mApBandPreferenceController = use(WifiTetherApBandPreferenceController.class); } @Override @@ -140,18 +145,17 @@ public class WifiTetherSettings extends RestrictedDashboardFragment @Override protected List createPreferenceControllers(Context context) { - final List controllers = new ArrayList<>(); - mSSIDPreferenceController = new WifiTetherSSIDPreferenceController(context, this); - mSecurityPreferenceController = new WifiTetherSecurityPreferenceController(context, this); - mPasswordPreferenceController = new WifiTetherPasswordPreferenceController(context, this); - mApBandPreferenceController = new WifiTetherApBandPreferenceController(context, this); + return buildPreferenceControllers(context, this::onTetherConfigUpdated); + } + + private static List buildPreferenceControllers(Context context, + WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) { + final List controllers = new ArrayList<>(); + controllers.add(new WifiTetherSSIDPreferenceController(context, listener)); + controllers.add(new WifiTetherSecurityPreferenceController(context, listener)); + controllers.add(new WifiTetherPasswordPreferenceController(context, listener)); + controllers.add(new WifiTetherApBandPreferenceController(context, listener)); - controllers.add(mSSIDPreferenceController); - controllers.add(mSecurityPreferenceController); - controllers.add(mPasswordPreferenceController); - controllers.add(mApBandPreferenceController); - controllers.add( - new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF)); return controllers; } @@ -227,6 +231,12 @@ public class WifiTetherSettings extends RestrictedDashboardFragment keys.add(KEY_WIFI_TETHER_SCREEN); return keys; } + + @Override + public List createPreferenceControllers( + Context context) { + return buildPreferenceControllers(context, null /* listener */); + } }; @VisibleForTesting diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java index 65f92a32727..d9bc486be5d 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiManager.java @@ -52,6 +52,11 @@ public class ShadowWifiManager extends org.robolectric.shadows.ShadowWifiManager return Collections.emptyList(); } + @Implementation + public boolean isDualModeSupported() { + return false; + } + public static ShadowWifiManager get() { return Shadow.extract(application.getSystemService(WifiManager.class)); } diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java index d376775d1a3..4b765e845ed 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -17,6 +17,7 @@ package com.android.settings.wifi.tether; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -27,6 +28,7 @@ import android.os.UserHandle; import android.os.UserManager; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowWifiManager; import org.junit.Before; import org.junit.Test; @@ -34,12 +36,15 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowWifiManager.class}) public class WifiTetherSettingsTest { + private static final String[] WIFI_REGEXS = {"wifi_regexs"}; private Context mContext; @@ -55,12 +60,12 @@ public class WifiTetherSettingsTest { MockitoAnnotations.initMocks(this); doReturn(mConnectivityManager) .when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE); - doReturn(mUserManager) - .when(mContext).getSystemService(Context.USER_SERVICE); + doReturn(WIFI_REGEXS).when(mConnectivityManager).getTetherableWifiRegexs(); + doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE); } @Test - public void testWifiTetherNonIndexableKeys_tetherAvailable_keysNotReturned() { + public void wifiTetherNonIndexableKeys_tetherAvailable_keysNotReturned() { // To let TetherUtil.isTetherAvailable return true, select one of the combinations setupIsTetherAvailable(true); @@ -74,7 +79,7 @@ public class WifiTetherSettingsTest { } @Test - public void testWifiTetherNonIndexableKeys_tetherNotAvailable_keysReturned() { + public void wifiTetherNonIndexableKeys_tetherNotAvailable_keysReturned() { // To let TetherUtil.isTetherAvailable return false, select one of the combinations setupIsTetherAvailable(false); @@ -87,6 +92,12 @@ public class WifiTetherSettingsTest { assertThat(niks).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_AP_BAND); } + @Test + public void createPreferenceControllers_notEmpty() { + assertThat(WifiTetherSettings.SEARCH_INDEX_DATA_PROVIDER.getPreferenceControllers(mContext)) + .isNotEmpty(); + } + private void setupIsTetherAvailable(boolean returnValue) { when(mConnectivityManager.isTetheringSupported()).thenReturn(true);