From 51a863301bb4f5ff21ac89f6efaabdb926fe3b23 Mon Sep 17 00:00:00 2001 From: Heemin Seog Date: Sun, 19 Apr 2020 01:14:12 -0700 Subject: Move things to com.android.systemui.car Bug: 154357193 Test: atest (all tests in CarSystemUITests) Change-Id: I897480b79c9c6dbf5e93b9eb869fe0acec77c598 --- .../res/layout/car_top_navigation_bar.xml | 4 +- .../car_top_navigation_bar_unprovisioned.xml | 4 +- packages/CarSystemUI/res/values/config.xml | 2 +- .../com/android/systemui/CarSystemUIBinder.java | 2 +- .../systemui/car/hvac/AnimatedTemperatureView.java | 277 +++++++++++++++++ .../android/systemui/car/hvac/HvacController.java | 221 ++++++++++++++ .../car/hvac/TemperatureBackgroundAnimator.java | 340 +++++++++++++++++++++ .../systemui/car/hvac/TemperatureColorStore.java | 202 ++++++++++++ .../systemui/car/hvac/TemperatureTextAnimator.java | 164 ++++++++++ .../systemui/car/hvac/TemperatureTextView.java | 88 ++++++ .../android/systemui/car/hvac/TemperatureView.java | 56 ++++ .../car/sideloaded/CarSideLoadedAppDetector.java | 136 +++++++++ .../ConnectedDeviceVoiceRecognitionNotifier.java | 95 ++++++ .../car/CarNavigationBarController.java | 2 +- .../navigationbar/car/hvac/HvacController.java | 221 -------------- .../car/hvac/TemperatureTextView.java | 88 ------ .../navigationbar/car/hvac/TemperatureView.java | 56 ---- .../sideloaded/car/CarSideLoadedAppDetector.java | 136 --------- .../statusbar/hvac/AnimatedTemperatureView.java | 278 ----------------- .../hvac/TemperatureBackgroundAnimator.java | 340 --------------------- .../statusbar/hvac/TemperatureColorStore.java | 202 ------------ .../statusbar/hvac/TemperatureTextAnimator.java | 164 ---------- .../ConnectedDeviceVoiceRecognitionNotifier.java | 95 ------ .../systemui/car/hvac/HvacControllerTest.java | 135 ++++++++ .../sideloaded/CarSideLoadedAppDetectorTest.java | 164 ++++++++++ ...onnectedDeviceVoiceRecognitionNotifierTest.java | 97 ++++++ .../car/CarNavigationBarControllerTest.java | 2 +- .../navigationbar/car/hvac/HvacControllerTest.java | 135 -------- .../car/CarSideLoadedAppDetectorTest.java | 164 ---------- ...onnectedDeviceVoiceRecognitionNotifierTest.java | 97 ------ 30 files changed, 1983 insertions(+), 1984 deletions(-) create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java delete mode 100644 packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java create mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java create mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java create mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java delete mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java delete mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java delete mode 100644 packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index ce0d31c6cc47..a7347f22f2e5 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -45,7 +45,7 @@ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" /> - - - - com.android.systemui.theme.ThemeOverlayController com.android.systemui.navigationbar.car.CarNavigationBar com.android.systemui.toast.ToastUI - com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier + com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier com.android.systemui.window.SystemUIOverlayWindowManager diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java index 59fa9d09c9ee..a69c92f0bd05 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java @@ -19,6 +19,7 @@ package com.android.systemui; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.car.notification.CarNotificationModule; +import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; @@ -38,7 +39,6 @@ import com.android.systemui.statusbar.tv.TvStatusBar; import com.android.systemui.theme.ThemeOverlayController; import com.android.systemui.toast.ToastUI; import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier; import com.android.systemui.volume.VolumeUI; import com.android.systemui.window.OverlayWindowModule; import com.android.systemui.window.SystemUIOverlayWindowManager; diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java new file mode 100644 index 000000000000..a7294317f46c --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.util.AttributeSet; +import android.util.Property; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextSwitcher; +import android.widget.TextView; + +import com.android.systemui.R; + +/** + * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in + * the XML. + * XML properties: + * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol) + * hvacOrientaion = Example: left + */ +public class AnimatedTemperatureView extends FrameLayout implements TemperatureView { + + private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f; + private static final Property COLOR_PROPERTY = + new Property(Integer.class, "color") { + + @Override + public Integer get(ColorDrawable object) { + return object.getColor(); + } + + @Override + public void set(ColorDrawable object, Integer value) { + object.setColor(value); + } + }; + + static boolean isHorizontal(int gravity) { + return Gravity.isHorizontal(gravity) + && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL; + } + + @SuppressLint("RtlHardcoded") + static boolean isLeft(int gravity, int layoutDirection) { + return Gravity + .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection) + == Gravity.LEFT; + } + + static boolean isVertical(int gravity) { + return Gravity.isVertical(gravity) + && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL; + } + + static boolean isTop(int gravity) { + return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP; + } + + private final int mAreaId; + private final int mPropertyId; + private final int mPivotOffset; + private final int mGravity; + private final int mTextAppearanceRes; + private final int mMinEms; + private final Rect mPaddingRect; + private final float mMinValue; + private final float mMaxValue; + + private final ColorDrawable mBackgroundColor; + + private final TemperatureColorStore mColorStore = new TemperatureColorStore(); + private final TemperatureBackgroundAnimator mBackgroundAnimator; + private final TemperatureTextAnimator mTextAnimator; + boolean mDisplayInFahrenheit = false; + + public AnimatedTemperatureView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray typedArray = context.obtainStyledAttributes(attrs, + R.styleable.AnimatedTemperatureView); + mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1); + mPropertyId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacPropertyId, -1); + mPivotOffset = + typedArray.getDimensionPixelOffset( + R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0); + mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity, + Gravity.START); + mTextAppearanceRes = + typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance, + 0); + mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0); + mMinValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMinValue, + Float.NaN); + mMaxValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMaxValue, + Float.NaN); + + + mPaddingRect = + new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); + setPadding(0, 0, 0, 0); + + setClipChildren(false); + setClipToPadding(false); + + // init Views + TextSwitcher textSwitcher = new TextSwitcher(context); + textSwitcher.setFactory(this::generateTextView); + ImageView background = new ImageView(context); + mBackgroundColor = new ColorDrawable(Color.TRANSPARENT); + background.setImageDrawable(mBackgroundColor); + background.setVisibility(View.GONE); + + mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background); + + + String format = typedArray.getString(R.styleable.AnimatedTemperatureView_hvacTempFormat); + format = (format == null) ? "%.1f\u00B0" : format; + CharSequence minText = typedArray.getString( + R.styleable.AnimatedTemperatureView_hvacMinText); + CharSequence maxText = typedArray.getString( + R.styleable.AnimatedTemperatureView_hvacMaxText); + mTextAnimator = new TemperatureTextAnimator(this, textSwitcher, format, mPivotOffset, + minText, maxText); + + addView(background, ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + + typedArray.recycle(); + } + + + private TextView generateTextView() { + TextView textView = new TextView(getContext()); + textView.setTextAppearance(mTextAppearanceRes); + textView.setAllCaps(true); + textView.setMinEms(mMinEms); + textView.setGravity(mGravity); + textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right, + mPaddingRect.bottom); + textView.getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (isHorizontal(mGravity)) { + if (isLeft(mGravity, getLayoutDirection())) { + textView.setPivotX(-mPivotOffset); + } else { + textView.setPivotX(textView.getWidth() + mPivotOffset); + } + } + textView.getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } + }); + textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + return textView; + } + + /** + * Formats the float for display + * + * @param temp - The current temp or NaN + */ + @Override + public void setTemp(float temp) { + if (mDisplayInFahrenheit) { + temp = convertToFahrenheit(temp); + } + mTextAnimator.setTemp(temp); + if (Float.isNaN(temp)) { + mBackgroundAnimator.hideCircle(); + return; + } + int color; + if (isMinValue(temp)) { + color = mColorStore.getMinColor(); + } else if (isMaxValue(temp)) { + color = mColorStore.getMaxColor(); + } else { + color = mColorStore.getColorForTemperature(temp); + } + if (mBackgroundAnimator.isOpen()) { + ObjectAnimator colorAnimator = + ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color); + colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore + .lerpColor(fraction, (int) startValue, (int) endValue)); + colorAnimator.start(); + } else { + mBackgroundColor.setColor(color); + } + + mBackgroundAnimator.animateOpen(); + } + + @Override + public void setDisplayInFahrenheit(boolean displayInFahrenheit) { + mDisplayInFahrenheit = displayInFahrenheit; + } + + boolean isMinValue(float temp) { + return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue); + } + + boolean isMaxValue(float temp) { + return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue); + } + + private boolean isApproxEqual(float left, float right) { + return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA; + } + + int getGravity() { + return mGravity; + } + + int getPivotOffset() { + return mPivotOffset; + } + + Rect getPaddingRect() { + return mPaddingRect; + } + + /** + * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (358614275) + */ + @Override + public int getPropertyId() { + return mPropertyId; + } + + /** + * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + */ + @Override + public int getAreaId() { + return mAreaId; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mBackgroundAnimator.stopAnimations(); + } + +} + diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java new file mode 100644 index 000000000000..af8ddb6a8180 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; +import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS; + +import android.car.Car; +import android.car.VehicleUnit; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.hvac.CarHvacManager; +import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.car.CarServiceProvider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Manages the connection to the Car service and delegates value changes to the registered + * {@link TemperatureView}s + */ +@Singleton +public class HvacController { + public static final String TAG = "HvacController"; + + private final CarServiceProvider mCarServiceProvider; + private final Set mRegisteredViews = new HashSet<>(); + + private CarHvacManager mHvacManager; + private HashMap> mTempComponents = new HashMap<>(); + + /** + * Callback for getting changes from {@link CarHvacManager} and setting the UI elements to + * match. + */ + private final CarHvacEventCallback mHardwareCallback = new CarHvacEventCallback() { + @Override + public void onChangeEvent(final CarPropertyValue val) { + try { + int areaId = val.getAreaId(); + int propertyId = val.getPropertyId(); + List temperatureViews = mTempComponents.get( + new HvacKey(propertyId, areaId)); + if (temperatureViews != null && !temperatureViews.isEmpty()) { + float value = (float) val.getValue(); + for (TemperatureView tempView : temperatureViews) { + tempView.setTemp(value); + } + } // else the data is not of interest + } catch (Exception e) { + // catch all so we don't take down the sysui if a new data type is + // introduced. + Log.e(TAG, "Failed handling hvac change event", e); + } + } + + @Override + public void onErrorEvent(final int propertyId, final int zone) { + Log.d(TAG, "HVAC error event, propertyId: " + propertyId + + " zone: " + zone); + } + }; + + private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = + car -> { + try { + mHvacManager = (CarHvacManager) car.getCarManager(Car.HVAC_SERVICE); + mHvacManager.registerCallback(mHardwareCallback); + initComponents(); + } catch (Exception e) { + Log.e(TAG, "Failed to correctly connect to HVAC", e); + } + }; + + @Inject + public HvacController(CarServiceProvider carServiceProvider) { + mCarServiceProvider = carServiceProvider; + } + + /** + * Create connection to the Car service. Note: call backs from the Car service + * ({@link CarHvacManager}) will happen on the same thread this method was called from. + */ + public void connectToCarService() { + mCarServiceProvider.addListener(mCarServiceLifecycleListener); + } + + /** + * Add component to list and initialize it if the connection is up. + */ + private void addHvacTextView(TemperatureView temperatureView) { + if (mRegisteredViews.contains(temperatureView)) { + return; + } + + HvacKey hvacKey = new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()); + if (!mTempComponents.containsKey(hvacKey)) { + mTempComponents.put(hvacKey, new ArrayList<>()); + } + mTempComponents.get(hvacKey).add(temperatureView); + initComponent(temperatureView); + + mRegisteredViews.add(temperatureView); + } + + private void initComponents() { + Iterator>> iterator = + mTempComponents.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + List temperatureViews = next.getValue(); + for (TemperatureView view : temperatureViews) { + initComponent(view); + } + } + } + + private void initComponent(TemperatureView view) { + int id = view.getPropertyId(); + int zone = view.getAreaId(); + + try { + if (mHvacManager != null + && mHvacManager.isPropertyAvailable(HVAC_TEMPERATURE_DISPLAY_UNITS, + VEHICLE_AREA_TYPE_GLOBAL)) { + if (mHvacManager.getIntProperty(HVAC_TEMPERATURE_DISPLAY_UNITS, + VEHICLE_AREA_TYPE_GLOBAL) == VehicleUnit.FAHRENHEIT) { + view.setDisplayInFahrenheit(true); + } + + } + if (mHvacManager == null || !mHvacManager.isPropertyAvailable(id, zone)) { + view.setTemp(Float.NaN); + return; + } + view.setTemp(mHvacManager.getFloatProperty(id, zone)); + } catch (Exception e) { + view.setTemp(Float.NaN); + Log.e(TAG, "Failed to get value from hvac service", e); + } + } + + /** + * Removes all registered components. This is useful if you need to rebuild the UI since + * components self register. + */ + public void removeAllComponents() { + mTempComponents.clear(); + mRegisteredViews.clear(); + } + + /** + * Iterate through a view, looking for {@link TemperatureView} instances and add them to the + * controller if found. + */ + public void addTemperatureViewToController(View v) { + if (v instanceof TemperatureView) { + addHvacTextView((TemperatureView) v); + } else if (v instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) v; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + addTemperatureViewToController(viewGroup.getChildAt(i)); + } + } + } + + /** + * Key for storing {@link TemperatureView}s in a hash map + */ + private static class HvacKey { + + int mPropertyId; + int mAreaId; + + private HvacKey(int propertyId, int areaId) { + mPropertyId = propertyId; + mAreaId = areaId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HvacKey hvacKey = (HvacKey) o; + return mPropertyId == hvacKey.mPropertyId + && mAreaId == hvacKey.mAreaId; + } + + @Override + public int hashCode() { + return Objects.hash(mPropertyId, mAreaId); + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java new file mode 100644 index 000000000000..a4c45730a9c2 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isTop; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isVertical; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.IntDef; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.animation.AnticipateInterpolator; +import android.widget.ImageView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Controls circular reveal animation of temperature background + */ +class TemperatureBackgroundAnimator { + + private static final AnticipateInterpolator ANTICIPATE_INTERPOLATOR = + new AnticipateInterpolator(); + private static final float MAX_OPACITY = .6f; + + private final View mAnimatedView; + + private int mPivotX; + private int mPivotY; + private int mGoneRadius; + private int mOvershootRadius; + private int mRestingRadius; + private int mBumpRadius; + + @CircleState + private int mCircleState; + + private Animator mCircularReveal; + private boolean mAnimationsReady; + + @IntDef({CircleState.GONE, CircleState.ENTERING, CircleState.OVERSHOT, CircleState.RESTING, + CircleState.RESTED, CircleState.BUMPING, CircleState.BUMPED, CircleState.EXITING}) + private @interface CircleState { + int GONE = 0; + int ENTERING = 1; + int OVERSHOT = 2; + int RESTING = 3; + int RESTED = 4; + int BUMPING = 5; + int BUMPED = 6; + int EXITING = 7; + } + + TemperatureBackgroundAnimator( + AnimatedTemperatureView parent, + ImageView animatedView) { + mAnimatedView = animatedView; + mAnimatedView.setAlpha(0); + + parent.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + setupAnimations(parent.getGravity(), parent.getPivotOffset(), + parent.getPaddingRect(), parent.getWidth(), parent.getHeight())); + } + + private void setupAnimations(int gravity, int pivotOffset, Rect paddingRect, + int width, int height) { + int padding; + if (isHorizontal(gravity)) { + mGoneRadius = pivotOffset; + if (isLeft(gravity, mAnimatedView.getLayoutDirection())) { + mPivotX = -pivotOffset; + padding = paddingRect.right; + } else { + mPivotX = width + pivotOffset; + padding = paddingRect.left; + } + mPivotY = height / 2; + mOvershootRadius = pivotOffset + width; + } else if (isVertical(gravity)) { + mGoneRadius = pivotOffset; + if (isTop(gravity)) { + mPivotY = -pivotOffset; + padding = paddingRect.bottom; + } else { + mPivotY = height + pivotOffset; + padding = paddingRect.top; + } + mPivotX = width / 2; + mOvershootRadius = pivotOffset + height; + } else { + mPivotX = width / 2; + mPivotY = height / 2; + mGoneRadius = 0; + if (width > height) { + mOvershootRadius = height; + padding = Math.max(paddingRect.top, paddingRect.bottom); + } else { + mOvershootRadius = width; + padding = Math.max(paddingRect.left, paddingRect.right); + } + } + mRestingRadius = mOvershootRadius - padding; + mBumpRadius = mOvershootRadius - padding / 3; + mAnimationsReady = true; + } + + boolean isOpen() { + return mCircleState != CircleState.GONE; + } + + void animateOpen() { + if (!mAnimationsReady + || !mAnimatedView.isAttachedToWindow() + || mCircleState == CircleState.ENTERING) { + return; + } + + AnimatorSet set = new AnimatorSet(); + List animators = new ArrayList<>(); + switch (mCircleState) { + case CircleState.ENTERING: + throw new AssertionError("Should not be able to reach this statement"); + case CircleState.GONE: { + Animator startCircle = createEnterAnimator(); + markState(startCircle, CircleState.ENTERING); + animators.add(startCircle); + Animator holdOvershoot = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius, + mOvershootRadius); + holdOvershoot.setDuration(50); + markState(holdOvershoot, CircleState.OVERSHOT); + animators.add(holdOvershoot); + Animator rest = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius, + mRestingRadius); + markState(rest, CircleState.RESTING); + animators.add(rest); + Animator holdRest = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius, + mRestingRadius); + markState(holdRest, CircleState.RESTED); + holdRest.setDuration(1000); + animators.add(holdRest); + Animator exit = createExitAnimator(mRestingRadius); + markState(exit, CircleState.EXITING); + animators.add(exit); + } + break; + case CircleState.RESTED: + case CircleState.RESTING: + case CircleState.EXITING: + case CircleState.OVERSHOT: + int startRadius = + mCircleState == CircleState.OVERSHOT ? mOvershootRadius : mRestingRadius; + Animator bump = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius, + mBumpRadius); + bump.setDuration(50); + markState(bump, CircleState.BUMPING); + animators.add(bump); + // fallthrough intentional + case CircleState.BUMPED: + case CircleState.BUMPING: + Animator holdBump = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius, + mBumpRadius); + holdBump.setDuration(100); + markState(holdBump, CircleState.BUMPED); + animators.add(holdBump); + Animator rest = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius, + mRestingRadius); + markState(rest, CircleState.RESTING); + animators.add(rest); + Animator holdRest = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius, + mRestingRadius); + holdRest.setDuration(1000); + markState(holdRest, CircleState.RESTED); + animators.add(holdRest); + Animator exit = createExitAnimator(mRestingRadius); + markState(exit, CircleState.EXITING); + animators.add(exit); + break; + } + set.playSequentially(animators); + set.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationStart(Animator animation) { + if (mCircularReveal != null) { + mCircularReveal.cancel(); + } + mCircularReveal = animation; + mAnimatedView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + return; + } + mCircularReveal = null; + mCircleState = CircleState.GONE; + mAnimatedView.setVisibility(View.GONE); + } + }); + + set.start(); + } + + private Animator createEnterAnimator() { + AnimatorSet animatorSet = new AnimatorSet(); + Animator circularReveal = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mGoneRadius, + mOvershootRadius); + Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, MAX_OPACITY); + animatorSet.playTogether(circularReveal, fade); + return animatorSet; + } + + private Animator createExitAnimator(int startRadius) { + AnimatorSet animatorSet = new AnimatorSet(); + Animator circularHide = ViewAnimationUtils + .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius, + (mGoneRadius + startRadius) / 2); + circularHide.setInterpolator(ANTICIPATE_INTERPOLATOR); + Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, 0); + fade.setStartDelay(50); + animatorSet.playTogether(circularHide, fade); + return animatorSet; + } + + void hideCircle() { + if (!mAnimationsReady || mCircleState == CircleState.GONE + || mCircleState == CircleState.EXITING) { + return; + } + + int startRadius; + switch (mCircleState) { + // Unreachable, but here to exhaust switch cases + //noinspection ConstantConditions + case CircleState.EXITING: + //noinspection ConstantConditions + case CircleState.GONE: + throw new AssertionError("Should not be able to reach this statement"); + case CircleState.BUMPED: + case CircleState.BUMPING: + startRadius = mBumpRadius; + break; + case CircleState.OVERSHOT: + startRadius = mOvershootRadius; + break; + case CircleState.ENTERING: + case CircleState.RESTED: + case CircleState.RESTING: + startRadius = mRestingRadius; + break; + default: + throw new IllegalStateException("Unknown CircleState: " + mCircleState); + } + + Animator hideAnimation = createExitAnimator(startRadius); + if (startRadius == mRestingRadius) { + hideAnimation.setInterpolator(ANTICIPATE_INTERPOLATOR); + } + hideAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationStart(Animator animation) { + mCircleState = CircleState.EXITING; + if (mCircularReveal != null) { + mCircularReveal.cancel(); + } + mCircularReveal = animation; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + return; + } + mCircularReveal = null; + mCircleState = CircleState.GONE; + mAnimatedView.setVisibility(View.GONE); + } + }); + hideAnimation.start(); + } + + void stopAnimations() { + if (mCircularReveal != null) { + mCircularReveal.end(); + } + } + + private void markState(Animator animator, @CircleState int startState) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mCircleState = startState; + } + }); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java new file mode 100644 index 000000000000..9a7b0b9819c5 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import android.graphics.Color; + +/** + * Contains the logic for mapping colors to temperatures + */ +class TemperatureColorStore { + + private static class TemperatureColorValue { + final float mTemperature; + final int mColor; + + private TemperatureColorValue(float temperature, int color) { + this.mTemperature = temperature; + this.mColor = color; + } + + float getTemperature() { + return mTemperature; + } + + int getColor() { + return mColor; + } + } + + private static TemperatureColorValue tempToColor(float temperature, int color) { + return new TemperatureColorValue(temperature, color); + } + + private static final int COLOR_COLDEST = 0xFF406DFF; + private static final int COLOR_COLD = 0xFF4094FF; + private static final int COLOR_NEUTRAL = 0xFFF4F4F4; + private static final int COLOR_WARM = 0xFFFF550F; + private static final int COLOR_WARMEST = 0xFFFF0000; + // must be sorted by temperature + private static final TemperatureColorValue[] sTemperatureColorValues = + { + // Celsius + tempToColor(19, COLOR_COLDEST), + tempToColor(21, COLOR_COLD), + tempToColor(23, COLOR_NEUTRAL), + tempToColor(25, COLOR_WARM), + tempToColor(27, COLOR_WARMEST), + + // Switch over + tempToColor(45, COLOR_WARMEST), + tempToColor(45.00001f, COLOR_COLDEST), + + // Farenheight + tempToColor(66, COLOR_COLDEST), + tempToColor(70, COLOR_COLD), + tempToColor(74, COLOR_NEUTRAL), + tempToColor(76, COLOR_WARM), + tempToColor(80, COLOR_WARMEST) + }; + + private static final int COLOR_UNSET = Color.BLACK; + + private final float[] mTempHsv1 = new float[3]; + private final float[] mTempHsv2 = new float[3]; + private final float[] mTempHsv3 = new float[3]; + + int getMinColor() { + return COLOR_COLDEST; + } + + int getMaxColor() { + return COLOR_WARMEST; + } + + int getColorForTemperature(float temperature) { + if (Float.isNaN(temperature)) { + return COLOR_UNSET; + } + TemperatureColorValue bottomValue = sTemperatureColorValues[0]; + if (temperature <= bottomValue.getTemperature()) { + return bottomValue.getColor(); + } + TemperatureColorValue topValue = + sTemperatureColorValues[sTemperatureColorValues.length - 1]; + if (temperature >= topValue.getTemperature()) { + return topValue.getColor(); + } + + int index = binarySearch(temperature); + if (index >= 0) { + return sTemperatureColorValues[index].getColor(); + } + + index = -index - 1; // move to the insertion point + + TemperatureColorValue startValue = sTemperatureColorValues[index - 1]; + TemperatureColorValue endValue = sTemperatureColorValues[index]; + float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature() + - startValue.getTemperature()); + return lerpColor(fraction, startValue.getColor(), endValue.getColor()); + } + + int lerpColor(float fraction, int startColor, int endColor) { + float[] startHsv = mTempHsv1; + Color.colorToHSV(startColor, startHsv); + float[] endHsv = mTempHsv2; + Color.colorToHSV(endColor, endHsv); + + // If a target color is white/gray, it should use the same hue as the other target + if (startHsv[1] == 0) { + startHsv[0] = endHsv[0]; + } + if (endHsv[1] == 0) { + endHsv[0] = startHsv[0]; + } + + float[] outColor = mTempHsv3; + outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]); + outColor[1] = lerp(fraction, startHsv[1], endHsv[1]); + outColor[2] = lerp(fraction, startHsv[2], endHsv[2]); + + return Color.HSVToColor(outColor); + } + + private float hueLerp(float fraction, float start, float end) { + // If in flat part of curve, no interpolation necessary + if (start == end) { + return start; + } + + // If the hues are more than 180 degrees apart, go the other way around the color wheel + // by moving the smaller value above 360 + if (Math.abs(start - end) > 180f) { + if (start < end) { + start += 360f; + } else { + end += 360f; + } + } + // Lerp and ensure the final output is within [0, 360) + return lerp(fraction, start, end) % 360f; + + } + + private float lerp(float fraction, float start, float end) { + // If in flat part of curve, no interpolation necessary + if (start == end) { + return start; + } + + // If outside bounds, use boundary value + if (fraction >= 1) { + return end; + } + if (fraction <= 0) { + return start; + } + + return (end - start) * fraction + start; + } + + private int binarySearch(float temperature) { + int low = 0; + int high = sTemperatureColorValues.length; + + while (low <= high) { + int mid = (low + high) >>> 1; + float midVal = sTemperatureColorValues[mid].getTemperature(); + + if (midVal < temperature) { + low = mid + 1; // Neither val is NaN, thisVal is smaller + } else if (midVal > temperature) { + high = mid - 1; // Neither val is NaN, thisVal is larger + } else { + int midBits = Float.floatToIntBits(midVal); + int keyBits = Float.floatToIntBits(temperature); + if (midBits == keyBits) { // Values are equal + return mid; // Key found + } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN) + low = mid + 1; + } else { /* (0.0, -0.0) or (NaN, !NaN)*/ + high = mid - 1; + } + } + } + return -(low + 1); // key not found. + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java new file mode 100644 index 000000000000..74d970464108 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft; + +import android.annotation.NonNull; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.RotateAnimation; +import android.view.animation.TranslateAnimation; +import android.widget.TextSwitcher; + +/** + * Controls animating TemperatureView's text + */ +class TemperatureTextAnimator { + + private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = + new DecelerateInterpolator(); + private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR = + new AccelerateDecelerateInterpolator(); + + private static final int ROTATION_DEGREES = 15; + private static final int DURATION_MILLIS = 200; + + private AnimatedTemperatureView mParent; + private final TextSwitcher mTextSwitcher; + private final String mTempFormat; + private final int mPivotOffset; + private final CharSequence mMinText; + private final CharSequence mMaxText; + + private Animation mTextInAnimationUp; + private Animation mTextOutAnimationUp; + private Animation mTextInAnimationDown; + private Animation mTextOutAnimationDown; + private Animation mTextFadeInAnimation; + private Animation mTextFadeOutAnimation; + + private float mLastTemp = Float.NaN; + + TemperatureTextAnimator(AnimatedTemperatureView parent, TextSwitcher textSwitcher, + String tempFormat, int pivotOffset, + CharSequence minText, CharSequence maxText) { + mParent = parent; + mTextSwitcher = textSwitcher; + mTempFormat = tempFormat; + mPivotOffset = pivotOffset; + mMinText = minText; + mMaxText = maxText; + + mParent.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + setupAnimations(mParent.getGravity())); + } + + void setTemp(float temp) { + if (Float.isNaN(temp)) { + mTextSwitcher.setInAnimation(mTextFadeInAnimation); + mTextSwitcher.setOutAnimation(mTextFadeOutAnimation); + mTextSwitcher.setText("--"); + mLastTemp = temp; + return; + } + boolean isMinValue = mParent.isMinValue(temp); + boolean isMaxValue = mParent.isMaxValue(temp); + if (Float.isNaN(mLastTemp)) { + mTextSwitcher.setInAnimation(mTextFadeInAnimation); + mTextSwitcher.setOutAnimation(mTextFadeOutAnimation); + } else if (!isMinValue && (isMaxValue || temp > mLastTemp)) { + mTextSwitcher.setInAnimation(mTextInAnimationUp); + mTextSwitcher.setOutAnimation(mTextOutAnimationUp); + } else { + mTextSwitcher.setInAnimation(mTextInAnimationDown); + mTextSwitcher.setOutAnimation(mTextOutAnimationDown); + } + CharSequence text; + if (isMinValue) { + text = mMinText; + } else if (isMaxValue) { + text = mMaxText; + } else { + text = String.format(mTempFormat, temp); + } + mTextSwitcher.setText(text); + mLastTemp = temp; + } + + private void setupAnimations(int gravity) { + mTextFadeInAnimation = createFadeAnimation(true); + mTextFadeOutAnimation = createFadeAnimation(false); + if (!isHorizontal(gravity)) { + mTextInAnimationUp = createTranslateFadeAnimation(true, true); + mTextOutAnimationUp = createTranslateFadeAnimation(false, true); + mTextInAnimationDown = createTranslateFadeAnimation(true, false); + mTextOutAnimationDown = createTranslateFadeAnimation(false, false); + } else { + boolean isLeft = isLeft(gravity, mTextSwitcher.getLayoutDirection()); + mTextInAnimationUp = createRotateFadeAnimation(true, isLeft, true); + mTextOutAnimationUp = createRotateFadeAnimation(false, isLeft, true); + mTextInAnimationDown = createRotateFadeAnimation(true, isLeft, false); + mTextOutAnimationDown = createRotateFadeAnimation(false, isLeft, false); + } + } + + @NonNull + private Animation createFadeAnimation(boolean in) { + AnimationSet set = new AnimationSet(true); + AlphaAnimation alphaAnimation = new AlphaAnimation(in ? 0 : 1, in ? 1 : 0); + alphaAnimation.setDuration(DURATION_MILLIS); + set.addAnimation(new RotateAnimation(0, 0)); // Undo any previous rotation + set.addAnimation(alphaAnimation); + return set; + } + + @NonNull + private Animation createTranslateFadeAnimation(boolean in, boolean up) { + AnimationSet set = new AnimationSet(true); + set.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR); + set.setDuration(DURATION_MILLIS); + int fromYDelta = in ? (up ? 1 : -1) : 0; + int toYDelta = in ? 0 : (up ? -1 : 1); + set.addAnimation( + new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, fromYDelta, Animation.RELATIVE_TO_SELF, + toYDelta)); + set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0)); + return set; + } + + @NonNull + private Animation createRotateFadeAnimation(boolean in, boolean isLeft, boolean up) { + AnimationSet set = new AnimationSet(true); + set.setInterpolator(DECELERATE_INTERPOLATOR); + set.setDuration(DURATION_MILLIS); + + float degrees = isLeft == up ? -ROTATION_DEGREES : ROTATION_DEGREES; + int pivotX = isLeft ? -mPivotOffset : mParent.getWidth() + mPivotOffset; + set.addAnimation( + new RotateAnimation(in ? -degrees : 0f, in ? 0f : degrees, Animation.ABSOLUTE, + pivotX, Animation.ABSOLUTE, 0f)); + set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0)); + return set; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java new file mode 100644 index 000000000000..521a665da5f6 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.systemui.R; + +/** + * Simple text display of HVAC properties, It is designed to show temperature and is configured in + * the XML. + * XML properties: + * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol) + */ +public class TemperatureTextView extends TextView implements TemperatureView { + + private final int mAreaId; + private final int mPropertyId; + private final String mTempFormat; + private boolean mDisplayFahrenheit = false; + + public TemperatureTextView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureView); + mAreaId = typedArray.getInt(R.styleable.TemperatureView_hvacAreaId, -1); + mPropertyId = typedArray.getInt(R.styleable.TemperatureView_hvacPropertyId, -1); + String format = typedArray.getString(R.styleable.TemperatureView_hvacTempFormat); + mTempFormat = (format == null) ? "%.1f\u00B0" : format; + } + + /** + * Formats the float for display + * + * @param temp - The current temp or NaN + */ + @Override + public void setTemp(float temp) { + if (Float.isNaN(temp)) { + setText("--"); + return; + } + if (mDisplayFahrenheit) { + temp = convertToFahrenheit(temp); + } + setText(String.format(mTempFormat, temp)); + } + + @Override + public void setDisplayInFahrenheit(boolean displayFahrenheit) { + mDisplayFahrenheit = displayFahrenheit; + } + + /** + * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + */ + @Override + public int getPropertyId() { + return mPropertyId; + } + + /** + * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + */ + @Override + public int getAreaId() { + return mAreaId; + } +} + diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java new file mode 100644 index 000000000000..6b903fad505c --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +/** + * Interface for Views that display temperature HVAC properties + */ +public interface TemperatureView { + /** + * Formats the float for display + * + * @param temp - The current temp in Celsius or NaN + */ + void setTemp(float temp); + + /** + * Render the displayed temperature in Fahrenheit + * + * @param displayFahrenheit - True if temperature should be displayed in Fahrenheit + */ + void setDisplayInFahrenheit(boolean displayFahrenheit); + + /** + * Convert the given temperature in Celsius into Fahrenheit + * + * @param realTemp - The temperature in Celsius + * @return Temperature in Fahrenheit. + */ + default float convertToFahrenheit(float realTemp) { + return (realTemp * 9f / 5f) + 32; + } + + /** + * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + */ + int getPropertyId(); + + /** + * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + */ + int getAreaId(); +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java new file mode 100644 index 000000000000..f145b148eaf7 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 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.car.sideloaded; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.UserHandle; +import android.util.Log; + +import com.android.systemui.R; +import com.android.systemui.car.CarDeviceProvisionedController; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * A class that detects unsafe apps. + * An app is considered safe if is a system app or installed through whitelisted sources. + */ +@Singleton +public class CarSideLoadedAppDetector { + private static final String TAG = "CarSideLoadedDetector"; + + private final PackageManager mPackageManager; + private final CarDeviceProvisionedController mCarDeviceProvisionedController; + private final List mAllowedAppInstallSources; + + @Inject + public CarSideLoadedAppDetector(@Main Resources resources, PackageManager packageManager, + CarDeviceProvisionedController deviceProvisionedController) { + mAllowedAppInstallSources = Arrays.asList( + resources.getStringArray(R.array.config_allowedAppInstallSources)); + mPackageManager = packageManager; + mCarDeviceProvisionedController = deviceProvisionedController; + } + + boolean hasUnsafeInstalledApps() { + int userId = mCarDeviceProvisionedController.getCurrentUser(); + + List packages = mPackageManager.getInstalledPackagesAsUser( + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + for (PackageInfo info : packages) { + if (info.applicationInfo == null) { + Log.w(TAG, info.packageName + " does not have application info."); + return true; + } + + if (!isSafe(info.applicationInfo)) { + return true; + } + } + return false; + } + + boolean isSafe(@NonNull ActivityManager.StackInfo stackInfo) { + ComponentName componentName = stackInfo.topActivity; + if (componentName == null) { + Log.w(TAG, "Stack info does not have top activity: " + stackInfo.stackId); + return false; + } + return isSafe(componentName.getPackageName()); + } + + private boolean isSafe(@NonNull String packageName) { + if (packageName == null) { + return false; + } + + ApplicationInfo applicationInfo; + try { + int userId = mCarDeviceProvisionedController.getCurrentUser(); + applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.of(userId)); + + if (applicationInfo == null) { + Log.e(TAG, packageName + " did not have an application info!"); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Could not get application info for package:" + packageName, e); + return false; + } + + return isSafe(applicationInfo); + } + + private boolean isSafe(@NonNull ApplicationInfo applicationInfo) { + String packageName = applicationInfo.packageName; + + if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { + return true; + } + + String initiatingPackageName; + try { + InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName); + initiatingPackageName = sourceInfo.getInitiatingPackageName(); + if (initiatingPackageName == null) { + Log.w(TAG, packageName + " does not have an installer name."); + return false; + } + + return mAllowedAppInstallSources.contains(initiatingPackageName); + } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) { + return false; + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java new file mode 100644 index 000000000000..c054d204af98 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 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.car.voicerecognition; + +import android.bluetooth.BluetoothHeadsetClient; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; +import android.widget.Toast; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; +import com.android.systemui.SysUIToast; +import com.android.systemui.SystemUI; +import com.android.systemui.dagger.qualifiers.Main; + +import javax.inject.Inject; + +/** + * Controller responsible for showing toast message when voice recognition over bluetooth device + * getting activated. + */ +public class ConnectedDeviceVoiceRecognitionNotifier extends SystemUI { + + private static final String TAG = "CarVoiceRecognition"; + @VisibleForTesting + static final int INVALID_VALUE = -1; + @VisibleForTesting + static final int VOICE_RECOGNITION_STARTED = 1; + + private Handler mHandler; + + private final BroadcastReceiver mVoiceRecognitionReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Voice recognition received an intent!"); + } + if (intent == null + || intent.getAction() == null + || !BluetoothHeadsetClient.ACTION_AG_EVENT.equals(intent.getAction()) + || !intent.hasExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION)) { + return; + } + + int voiceRecognitionState = intent.getIntExtra( + BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE); + + if (voiceRecognitionState == VOICE_RECOGNITION_STARTED) { + showToastMessage(); + } + } + }; + + private void showToastMessage() { + mHandler.post(() -> SysUIToast.makeText(mContext, R.string.voice_recognition_toast, + Toast.LENGTH_LONG).show()); + } + + @Inject + public ConnectedDeviceVoiceRecognitionNotifier(Context context, @Main Handler handler) { + super(context); + mHandler = handler; + } + + @Override + public void start() { + } + + @Override + protected void onBootCompleted() { + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT); + mContext.registerReceiverAsUser(mVoiceRecognitionReceiver, UserHandle.ALL, filter, + /* broadcastPermission= */ null, /* scheduler= */ null); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java index 37a82255929a..8f3ae1a25441 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java @@ -24,7 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.R; -import com.android.systemui.navigationbar.car.hvac.HvacController; +import com.android.systemui.car.hvac.HvacController; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java deleted file mode 100644 index fd9c488278ba..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2019 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.navigationbar.car.hvac; - -import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; -import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS; - -import android.car.Car; -import android.car.VehicleUnit; -import android.car.hardware.CarPropertyValue; -import android.car.hardware.hvac.CarHvacManager; -import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.car.CarServiceProvider; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Manages the connection to the Car service and delegates value changes to the registered - * {@link TemperatureView}s - */ -@Singleton -public class HvacController { - public static final String TAG = "HvacController"; - - private final CarServiceProvider mCarServiceProvider; - private final Set mRegisteredViews = new HashSet<>(); - - private CarHvacManager mHvacManager; - private HashMap> mTempComponents = new HashMap<>(); - - /** - * Callback for getting changes from {@link CarHvacManager} and setting the UI elements to - * match. - */ - private final CarHvacEventCallback mHardwareCallback = new CarHvacEventCallback() { - @Override - public void onChangeEvent(final CarPropertyValue val) { - try { - int areaId = val.getAreaId(); - int propertyId = val.getPropertyId(); - List temperatureViews = mTempComponents.get( - new HvacKey(propertyId, areaId)); - if (temperatureViews != null && !temperatureViews.isEmpty()) { - float value = (float) val.getValue(); - for (TemperatureView tempView : temperatureViews) { - tempView.setTemp(value); - } - } // else the data is not of interest - } catch (Exception e) { - // catch all so we don't take down the sysui if a new data type is - // introduced. - Log.e(TAG, "Failed handling hvac change event", e); - } - } - - @Override - public void onErrorEvent(final int propertyId, final int zone) { - Log.d(TAG, "HVAC error event, propertyId: " + propertyId - + " zone: " + zone); - } - }; - - private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = - car -> { - try { - mHvacManager = (CarHvacManager) car.getCarManager(Car.HVAC_SERVICE); - mHvacManager.registerCallback(mHardwareCallback); - initComponents(); - } catch (Exception e) { - Log.e(TAG, "Failed to correctly connect to HVAC", e); - } - }; - - @Inject - public HvacController(CarServiceProvider carServiceProvider) { - mCarServiceProvider = carServiceProvider; - } - - /** - * Create connection to the Car service. Note: call backs from the Car service - * ({@link CarHvacManager}) will happen on the same thread this method was called from. - */ - public void connectToCarService() { - mCarServiceProvider.addListener(mCarServiceLifecycleListener); - } - - /** - * Add component to list and initialize it if the connection is up. - */ - private void addHvacTextView(TemperatureView temperatureView) { - if (mRegisteredViews.contains(temperatureView)) { - return; - } - - HvacKey hvacKey = new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()); - if (!mTempComponents.containsKey(hvacKey)) { - mTempComponents.put(hvacKey, new ArrayList<>()); - } - mTempComponents.get(hvacKey).add(temperatureView); - initComponent(temperatureView); - - mRegisteredViews.add(temperatureView); - } - - private void initComponents() { - Iterator>> iterator = - mTempComponents.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry> next = iterator.next(); - List temperatureViews = next.getValue(); - for (TemperatureView view : temperatureViews) { - initComponent(view); - } - } - } - - private void initComponent(TemperatureView view) { - int id = view.getPropertyId(); - int zone = view.getAreaId(); - - try { - if (mHvacManager != null - && mHvacManager.isPropertyAvailable(HVAC_TEMPERATURE_DISPLAY_UNITS, - VEHICLE_AREA_TYPE_GLOBAL)) { - if (mHvacManager.getIntProperty(HVAC_TEMPERATURE_DISPLAY_UNITS, - VEHICLE_AREA_TYPE_GLOBAL) == VehicleUnit.FAHRENHEIT) { - view.setDisplayInFahrenheit(true); - } - - } - if (mHvacManager == null || !mHvacManager.isPropertyAvailable(id, zone)) { - view.setTemp(Float.NaN); - return; - } - view.setTemp(mHvacManager.getFloatProperty(id, zone)); - } catch (Exception e) { - view.setTemp(Float.NaN); - Log.e(TAG, "Failed to get value from hvac service", e); - } - } - - /** - * Removes all registered components. This is useful if you need to rebuild the UI since - * components self register. - */ - public void removeAllComponents() { - mTempComponents.clear(); - mRegisteredViews.clear(); - } - - /** - * Iterate through a view, looking for {@link TemperatureView} instances and add them to the - * controller if found. - */ - public void addTemperatureViewToController(View v) { - if (v instanceof TemperatureView) { - addHvacTextView((TemperatureView) v); - } else if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - addTemperatureViewToController(viewGroup.getChildAt(i)); - } - } - } - - /** - * Key for storing {@link TemperatureView}s in a hash map - */ - private static class HvacKey { - - int mPropertyId; - int mAreaId; - - private HvacKey(int propertyId, int areaId) { - mPropertyId = propertyId; - mAreaId = areaId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HvacKey hvacKey = (HvacKey) o; - return mPropertyId == hvacKey.mPropertyId - && mAreaId == hvacKey.mAreaId; - } - - @Override - public int hashCode() { - return Objects.hash(mPropertyId, mAreaId); - } - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java deleted file mode 100644 index ad4fcd9b67da..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 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.navigationbar.car.hvac; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.android.systemui.R; - -/** - * Simple text display of HVAC properties, It is designed to show temperature and is configured in - * the XML. - * XML properties: - * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) - * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) - * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol) - */ -public class TemperatureTextView extends TextView implements TemperatureView { - - private final int mAreaId; - private final int mPropertyId; - private final String mTempFormat; - private boolean mDisplayFahrenheit = false; - - public TemperatureTextView(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureView); - mAreaId = typedArray.getInt(R.styleable.TemperatureView_hvacAreaId, -1); - mPropertyId = typedArray.getInt(R.styleable.TemperatureView_hvacPropertyId, -1); - String format = typedArray.getString(R.styleable.TemperatureView_hvacTempFormat); - mTempFormat = (format == null) ? "%.1f\u00B0" : format; - } - - /** - * Formats the float for display - * - * @param temp - The current temp or NaN - */ - @Override - public void setTemp(float temp) { - if (Float.isNaN(temp)) { - setText("--"); - return; - } - if (mDisplayFahrenheit) { - temp = convertToFahrenheit(temp); - } - setText(String.format(mTempFormat, temp)); - } - - @Override - public void setDisplayInFahrenheit(boolean displayFahrenheit) { - mDisplayFahrenheit = displayFahrenheit; - } - - /** - * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) - */ - @Override - public int getPropertyId() { - return mPropertyId; - } - - /** - * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) - */ - @Override - public int getAreaId() { - return mAreaId; - } -} - diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java deleted file mode 100644 index 963f3184c40d..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2019 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.navigationbar.car.hvac; - -/** - * Interface for Views that display temperature HVAC properties - */ -public interface TemperatureView { - /** - * Formats the float for display - * - * @param temp - The current temp in Celsius or NaN - */ - void setTemp(float temp); - - /** - * Render the displayed temperature in Fahrenheit - * - * @param displayFahrenheit - True if temperature should be displayed in Fahrenheit - */ - void setDisplayInFahrenheit(boolean displayFahrenheit); - - /** - * Convert the given temperature in Celsius into Fahrenheit - * - * @param realTemp - The temperature in Celsius - * @return Temperature in Fahrenheit. - */ - default float convertToFahrenheit(float realTemp) { - return (realTemp * 9f / 5f) + 32; - } - - /** - * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) - */ - int getPropertyId(); - - /** - * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) - */ - int getAreaId(); -} diff --git a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java deleted file mode 100644 index c0dbb5879d7d..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2020 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.sideloaded.car; - -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.pm.ApplicationInfo; -import android.content.pm.InstallSourceInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.UserHandle; -import android.util.Log; - -import com.android.systemui.R; -import com.android.systemui.car.CarDeviceProvisionedController; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.Arrays; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * A class that detects unsafe apps. - * An app is considered safe if is a system app or installed through whitelisted sources. - */ -@Singleton -public class CarSideLoadedAppDetector { - private static final String TAG = "CarSideLoadedDetector"; - - private final PackageManager mPackageManager; - private final CarDeviceProvisionedController mCarDeviceProvisionedController; - private final List mAllowedAppInstallSources; - - @Inject - public CarSideLoadedAppDetector(@Main Resources resources, PackageManager packageManager, - CarDeviceProvisionedController deviceProvisionedController) { - mAllowedAppInstallSources = Arrays.asList( - resources.getStringArray(R.array.config_allowedAppInstallSources)); - mPackageManager = packageManager; - mCarDeviceProvisionedController = deviceProvisionedController; - } - - boolean hasUnsafeInstalledApps() { - int userId = mCarDeviceProvisionedController.getCurrentUser(); - - List packages = mPackageManager.getInstalledPackagesAsUser( - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - userId); - for (PackageInfo info : packages) { - if (info.applicationInfo == null) { - Log.w(TAG, info.packageName + " does not have application info."); - return true; - } - - if (!isSafe(info.applicationInfo)) { - return true; - } - } - return false; - } - - boolean isSafe(@NonNull ActivityManager.StackInfo stackInfo) { - ComponentName componentName = stackInfo.topActivity; - if (componentName == null) { - Log.w(TAG, "Stack info does not have top activity: " + stackInfo.stackId); - return false; - } - return isSafe(componentName.getPackageName()); - } - - private boolean isSafe(@NonNull String packageName) { - if (packageName == null) { - return false; - } - - ApplicationInfo applicationInfo; - try { - int userId = mCarDeviceProvisionedController.getCurrentUser(); - applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - UserHandle.of(userId)); - - if (applicationInfo == null) { - Log.e(TAG, packageName + " did not have an application info!"); - return false; - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Could not get application info for package:" + packageName, e); - return false; - } - - return isSafe(applicationInfo); - } - - private boolean isSafe(@NonNull ApplicationInfo applicationInfo) { - String packageName = applicationInfo.packageName; - - if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { - return true; - } - - String initiatingPackageName; - try { - InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName); - initiatingPackageName = sourceInfo.getInitiatingPackageName(); - if (initiatingPackageName == null) { - Log.w(TAG, packageName + " does not have an installer name."); - return false; - } - - return mAllowedAppInstallSources.contains(initiatingPackageName); - } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) { - return false; - } - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java deleted file mode 100644 index 908aaad71893..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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.systemui.statusbar.hvac; - -import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.util.AttributeSet; -import android.util.Property; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextSwitcher; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.navigationbar.car.hvac.TemperatureView; - -/** - * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in - * the XML. - * XML properties: - * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) - * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) - * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol) - * hvacOrientaion = Example: left - */ -public class AnimatedTemperatureView extends FrameLayout implements TemperatureView { - - private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f; - private static final Property COLOR_PROPERTY = - new Property(Integer.class, "color") { - - @Override - public Integer get(ColorDrawable object) { - return object.getColor(); - } - - @Override - public void set(ColorDrawable object, Integer value) { - object.setColor(value); - } - }; - - static boolean isHorizontal(int gravity) { - return Gravity.isHorizontal(gravity) - && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL; - } - - @SuppressLint("RtlHardcoded") - static boolean isLeft(int gravity, int layoutDirection) { - return Gravity - .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection) - == Gravity.LEFT; - } - - static boolean isVertical(int gravity) { - return Gravity.isVertical(gravity) - && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL; - } - - static boolean isTop(int gravity) { - return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP; - } - - private final int mAreaId; - private final int mPropertyId; - private final int mPivotOffset; - private final int mGravity; - private final int mTextAppearanceRes; - private final int mMinEms; - private final Rect mPaddingRect; - private final float mMinValue; - private final float mMaxValue; - - private final ColorDrawable mBackgroundColor; - - private final TemperatureColorStore mColorStore = new TemperatureColorStore(); - private final TemperatureBackgroundAnimator mBackgroundAnimator; - private final TemperatureTextAnimator mTextAnimator; - boolean mDisplayInFahrenheit = false; - - public AnimatedTemperatureView(Context context, AttributeSet attrs) { - super(context, attrs); - TypedArray typedArray = context.obtainStyledAttributes(attrs, - R.styleable.AnimatedTemperatureView); - mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1); - mPropertyId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacPropertyId, -1); - mPivotOffset = - typedArray.getDimensionPixelOffset( - R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0); - mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity, - Gravity.START); - mTextAppearanceRes = - typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance, - 0); - mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0); - mMinValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMinValue, - Float.NaN); - mMaxValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMaxValue, - Float.NaN); - - - mPaddingRect = - new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); - setPadding(0, 0, 0, 0); - - setClipChildren(false); - setClipToPadding(false); - - // init Views - TextSwitcher textSwitcher = new TextSwitcher(context); - textSwitcher.setFactory(this::generateTextView); - ImageView background = new ImageView(context); - mBackgroundColor = new ColorDrawable(Color.TRANSPARENT); - background.setImageDrawable(mBackgroundColor); - background.setVisibility(View.GONE); - - mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background); - - - String format = typedArray.getString(R.styleable.AnimatedTemperatureView_hvacTempFormat); - format = (format == null) ? "%.1f\u00B0" : format; - CharSequence minText = typedArray.getString( - R.styleable.AnimatedTemperatureView_hvacMinText); - CharSequence maxText = typedArray.getString( - R.styleable.AnimatedTemperatureView_hvacMaxText); - mTextAnimator = new TemperatureTextAnimator(this, textSwitcher, format, mPivotOffset, - minText, maxText); - - addView(background, ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - - typedArray.recycle(); - } - - - private TextView generateTextView() { - TextView textView = new TextView(getContext()); - textView.setTextAppearance(mTextAppearanceRes); - textView.setAllCaps(true); - textView.setMinEms(mMinEms); - textView.setGravity(mGravity); - textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right, - mPaddingRect.bottom); - textView.getViewTreeObserver() - .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (isHorizontal(mGravity)) { - if (isLeft(mGravity, getLayoutDirection())) { - textView.setPivotX(-mPivotOffset); - } else { - textView.setPivotX(textView.getWidth() + mPivotOffset); - } - } - textView.getViewTreeObserver().removeOnPreDrawListener(this); - return true; - } - }); - textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - - return textView; - } - - /** - * Formats the float for display - * - * @param temp - The current temp or NaN - */ - @Override - public void setTemp(float temp) { - if (mDisplayInFahrenheit) { - temp = convertToFahrenheit(temp); - } - mTextAnimator.setTemp(temp); - if (Float.isNaN(temp)) { - mBackgroundAnimator.hideCircle(); - return; - } - int color; - if (isMinValue(temp)) { - color = mColorStore.getMinColor(); - } else if (isMaxValue(temp)) { - color = mColorStore.getMaxColor(); - } else { - color = mColorStore.getColorForTemperature(temp); - } - if (mBackgroundAnimator.isOpen()) { - ObjectAnimator colorAnimator = - ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color); - colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore - .lerpColor(fraction, (int) startValue, (int) endValue)); - colorAnimator.start(); - } else { - mBackgroundColor.setColor(color); - } - - mBackgroundAnimator.animateOpen(); - } - - @Override - public void setDisplayInFahrenheit(boolean displayInFahrenheit) { - mDisplayInFahrenheit = displayInFahrenheit; - } - - boolean isMinValue(float temp) { - return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue); - } - - boolean isMaxValue(float temp) { - return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue); - } - - private boolean isApproxEqual(float left, float right) { - return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA; - } - - int getGravity() { - return mGravity; - } - - int getPivotOffset() { - return mPivotOffset; - } - - Rect getPaddingRect() { - return mPaddingRect; - } - - /** - * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (358614275) - */ - @Override - public int getPropertyId() { - return mPropertyId; - } - - /** - * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) - */ - @Override - public int getAreaId() { - return mAreaId; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mBackgroundAnimator.stopAnimations(); - } - -} - diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java deleted file mode 100644 index 3c6d623c8ff7..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 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.systemui.statusbar.hvac; - -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.annotation.IntDef; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.animation.AnticipateInterpolator; -import android.widget.ImageView; - -import java.util.ArrayList; -import java.util.List; - -/** - * Controls circular reveal animation of temperature background - */ -class TemperatureBackgroundAnimator { - - private static final AnticipateInterpolator ANTICIPATE_INTERPOLATOR = - new AnticipateInterpolator(); - private static final float MAX_OPACITY = .6f; - - private final View mAnimatedView; - - private int mPivotX; - private int mPivotY; - private int mGoneRadius; - private int mOvershootRadius; - private int mRestingRadius; - private int mBumpRadius; - - @CircleState - private int mCircleState; - - private Animator mCircularReveal; - private boolean mAnimationsReady; - - @IntDef({CircleState.GONE, CircleState.ENTERING, CircleState.OVERSHOT, CircleState.RESTING, - CircleState.RESTED, CircleState.BUMPING, CircleState.BUMPED, CircleState.EXITING}) - private @interface CircleState { - int GONE = 0; - int ENTERING = 1; - int OVERSHOT = 2; - int RESTING = 3; - int RESTED = 4; - int BUMPING = 5; - int BUMPED = 6; - int EXITING = 7; - } - - TemperatureBackgroundAnimator( - AnimatedTemperatureView parent, - ImageView animatedView) { - mAnimatedView = animatedView; - mAnimatedView.setAlpha(0); - - parent.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - setupAnimations(parent.getGravity(), parent.getPivotOffset(), - parent.getPaddingRect(), parent.getWidth(), parent.getHeight())); - } - - private void setupAnimations(int gravity, int pivotOffset, Rect paddingRect, - int width, int height) { - int padding; - if (isHorizontal(gravity)) { - mGoneRadius = pivotOffset; - if (isLeft(gravity, mAnimatedView.getLayoutDirection())) { - mPivotX = -pivotOffset; - padding = paddingRect.right; - } else { - mPivotX = width + pivotOffset; - padding = paddingRect.left; - } - mPivotY = height / 2; - mOvershootRadius = pivotOffset + width; - } else if (isVertical(gravity)) { - mGoneRadius = pivotOffset; - if (isTop(gravity)) { - mPivotY = -pivotOffset; - padding = paddingRect.bottom; - } else { - mPivotY = height + pivotOffset; - padding = paddingRect.top; - } - mPivotX = width / 2; - mOvershootRadius = pivotOffset + height; - } else { - mPivotX = width / 2; - mPivotY = height / 2; - mGoneRadius = 0; - if (width > height) { - mOvershootRadius = height; - padding = Math.max(paddingRect.top, paddingRect.bottom); - } else { - mOvershootRadius = width; - padding = Math.max(paddingRect.left, paddingRect.right); - } - } - mRestingRadius = mOvershootRadius - padding; - mBumpRadius = mOvershootRadius - padding / 3; - mAnimationsReady = true; - } - - boolean isOpen() { - return mCircleState != CircleState.GONE; - } - - void animateOpen() { - if (!mAnimationsReady - || !mAnimatedView.isAttachedToWindow() - || mCircleState == CircleState.ENTERING) { - return; - } - - AnimatorSet set = new AnimatorSet(); - List animators = new ArrayList<>(); - switch (mCircleState) { - case CircleState.ENTERING: - throw new AssertionError("Should not be able to reach this statement"); - case CircleState.GONE: { - Animator startCircle = createEnterAnimator(); - markState(startCircle, CircleState.ENTERING); - animators.add(startCircle); - Animator holdOvershoot = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius, - mOvershootRadius); - holdOvershoot.setDuration(50); - markState(holdOvershoot, CircleState.OVERSHOT); - animators.add(holdOvershoot); - Animator rest = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius, - mRestingRadius); - markState(rest, CircleState.RESTING); - animators.add(rest); - Animator holdRest = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius, - mRestingRadius); - markState(holdRest, CircleState.RESTED); - holdRest.setDuration(1000); - animators.add(holdRest); - Animator exit = createExitAnimator(mRestingRadius); - markState(exit, CircleState.EXITING); - animators.add(exit); - } - break; - case CircleState.RESTED: - case CircleState.RESTING: - case CircleState.EXITING: - case CircleState.OVERSHOT: - int startRadius = - mCircleState == CircleState.OVERSHOT ? mOvershootRadius : mRestingRadius; - Animator bump = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius, - mBumpRadius); - bump.setDuration(50); - markState(bump, CircleState.BUMPING); - animators.add(bump); - // fallthrough intentional - case CircleState.BUMPED: - case CircleState.BUMPING: - Animator holdBump = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius, - mBumpRadius); - holdBump.setDuration(100); - markState(holdBump, CircleState.BUMPED); - animators.add(holdBump); - Animator rest = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius, - mRestingRadius); - markState(rest, CircleState.RESTING); - animators.add(rest); - Animator holdRest = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius, - mRestingRadius); - holdRest.setDuration(1000); - markState(holdRest, CircleState.RESTED); - animators.add(holdRest); - Animator exit = createExitAnimator(mRestingRadius); - markState(exit, CircleState.EXITING); - animators.add(exit); - break; - } - set.playSequentially(animators); - set.addListener(new AnimatorListenerAdapter() { - private boolean mCanceled = false; - - @Override - public void onAnimationStart(Animator animation) { - if (mCircularReveal != null) { - mCircularReveal.cancel(); - } - mCircularReveal = animation; - mAnimatedView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationCancel(Animator animation) { - mCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCanceled) { - return; - } - mCircularReveal = null; - mCircleState = CircleState.GONE; - mAnimatedView.setVisibility(View.GONE); - } - }); - - set.start(); - } - - private Animator createEnterAnimator() { - AnimatorSet animatorSet = new AnimatorSet(); - Animator circularReveal = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mGoneRadius, - mOvershootRadius); - Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, MAX_OPACITY); - animatorSet.playTogether(circularReveal, fade); - return animatorSet; - } - - private Animator createExitAnimator(int startRadius) { - AnimatorSet animatorSet = new AnimatorSet(); - Animator circularHide = ViewAnimationUtils - .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius, - (mGoneRadius + startRadius) / 2); - circularHide.setInterpolator(ANTICIPATE_INTERPOLATOR); - Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, 0); - fade.setStartDelay(50); - animatorSet.playTogether(circularHide, fade); - return animatorSet; - } - - void hideCircle() { - if (!mAnimationsReady || mCircleState == CircleState.GONE - || mCircleState == CircleState.EXITING) { - return; - } - - int startRadius; - switch (mCircleState) { - // Unreachable, but here to exhaust switch cases - //noinspection ConstantConditions - case CircleState.EXITING: - //noinspection ConstantConditions - case CircleState.GONE: - throw new AssertionError("Should not be able to reach this statement"); - case CircleState.BUMPED: - case CircleState.BUMPING: - startRadius = mBumpRadius; - break; - case CircleState.OVERSHOT: - startRadius = mOvershootRadius; - break; - case CircleState.ENTERING: - case CircleState.RESTED: - case CircleState.RESTING: - startRadius = mRestingRadius; - break; - default: - throw new IllegalStateException("Unknown CircleState: " + mCircleState); - } - - Animator hideAnimation = createExitAnimator(startRadius); - if (startRadius == mRestingRadius) { - hideAnimation.setInterpolator(ANTICIPATE_INTERPOLATOR); - } - hideAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCanceled = false; - - @Override - public void onAnimationStart(Animator animation) { - mCircleState = CircleState.EXITING; - if (mCircularReveal != null) { - mCircularReveal.cancel(); - } - mCircularReveal = animation; - } - - @Override - public void onAnimationCancel(Animator animation) { - mCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCanceled) { - return; - } - mCircularReveal = null; - mCircleState = CircleState.GONE; - mAnimatedView.setVisibility(View.GONE); - } - }); - hideAnimation.start(); - } - - void stopAnimations() { - if (mCircularReveal != null) { - mCircularReveal.end(); - } - } - - private void markState(Animator animator, @CircleState int startState) { - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mCircleState = startState; - } - }); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java deleted file mode 100644 index a40ffaf850c5..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.systemui.statusbar.hvac; - -import android.graphics.Color; - -/** - * Contains the logic for mapping colors to temperatures - */ -class TemperatureColorStore { - - private static class TemperatureColorValue { - final float mTemperature; - final int mColor; - - private TemperatureColorValue(float temperature, int color) { - this.mTemperature = temperature; - this.mColor = color; - } - - float getTemperature() { - return mTemperature; - } - - int getColor() { - return mColor; - } - } - - private static TemperatureColorValue tempToColor(float temperature, int color) { - return new TemperatureColorValue(temperature, color); - } - - private static final int COLOR_COLDEST = 0xFF406DFF; - private static final int COLOR_COLD = 0xFF4094FF; - private static final int COLOR_NEUTRAL = 0xFFF4F4F4; - private static final int COLOR_WARM = 0xFFFF550F; - private static final int COLOR_WARMEST = 0xFFFF0000; - // must be sorted by temperature - private static final TemperatureColorValue[] sTemperatureColorValues = - { - // Celsius - tempToColor(19, COLOR_COLDEST), - tempToColor(21, COLOR_COLD), - tempToColor(23, COLOR_NEUTRAL), - tempToColor(25, COLOR_WARM), - tempToColor(27, COLOR_WARMEST), - - // Switch over - tempToColor(45, COLOR_WARMEST), - tempToColor(45.00001f, COLOR_COLDEST), - - // Farenheight - tempToColor(66, COLOR_COLDEST), - tempToColor(70, COLOR_COLD), - tempToColor(74, COLOR_NEUTRAL), - tempToColor(76, COLOR_WARM), - tempToColor(80, COLOR_WARMEST) - }; - - private static final int COLOR_UNSET = Color.BLACK; - - private final float[] mTempHsv1 = new float[3]; - private final float[] mTempHsv2 = new float[3]; - private final float[] mTempHsv3 = new float[3]; - - int getMinColor() { - return COLOR_COLDEST; - } - - int getMaxColor() { - return COLOR_WARMEST; - } - - int getColorForTemperature(float temperature) { - if (Float.isNaN(temperature)) { - return COLOR_UNSET; - } - TemperatureColorValue bottomValue = sTemperatureColorValues[0]; - if (temperature <= bottomValue.getTemperature()) { - return bottomValue.getColor(); - } - TemperatureColorValue topValue = - sTemperatureColorValues[sTemperatureColorValues.length - 1]; - if (temperature >= topValue.getTemperature()) { - return topValue.getColor(); - } - - int index = binarySearch(temperature); - if (index >= 0) { - return sTemperatureColorValues[index].getColor(); - } - - index = -index - 1; // move to the insertion point - - TemperatureColorValue startValue = sTemperatureColorValues[index - 1]; - TemperatureColorValue endValue = sTemperatureColorValues[index]; - float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature() - - startValue.getTemperature()); - return lerpColor(fraction, startValue.getColor(), endValue.getColor()); - } - - int lerpColor(float fraction, int startColor, int endColor) { - float[] startHsv = mTempHsv1; - Color.colorToHSV(startColor, startHsv); - float[] endHsv = mTempHsv2; - Color.colorToHSV(endColor, endHsv); - - // If a target color is white/gray, it should use the same hue as the other target - if (startHsv[1] == 0) { - startHsv[0] = endHsv[0]; - } - if (endHsv[1] == 0) { - endHsv[0] = startHsv[0]; - } - - float[] outColor = mTempHsv3; - outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]); - outColor[1] = lerp(fraction, startHsv[1], endHsv[1]); - outColor[2] = lerp(fraction, startHsv[2], endHsv[2]); - - return Color.HSVToColor(outColor); - } - - private float hueLerp(float fraction, float start, float end) { - // If in flat part of curve, no interpolation necessary - if (start == end) { - return start; - } - - // If the hues are more than 180 degrees apart, go the other way around the color wheel - // by moving the smaller value above 360 - if (Math.abs(start - end) > 180f) { - if (start < end) { - start += 360f; - } else { - end += 360f; - } - } - // Lerp and ensure the final output is within [0, 360) - return lerp(fraction, start, end) % 360f; - - } - - private float lerp(float fraction, float start, float end) { - // If in flat part of curve, no interpolation necessary - if (start == end) { - return start; - } - - // If outside bounds, use boundary value - if (fraction >= 1) { - return end; - } - if (fraction <= 0) { - return start; - } - - return (end - start) * fraction + start; - } - - private int binarySearch(float temperature) { - int low = 0; - int high = sTemperatureColorValues.length; - - while (low <= high) { - int mid = (low + high) >>> 1; - float midVal = sTemperatureColorValues[mid].getTemperature(); - - if (midVal < temperature) { - low = mid + 1; // Neither val is NaN, thisVal is smaller - } else if (midVal > temperature) { - high = mid - 1; // Neither val is NaN, thisVal is larger - } else { - int midBits = Float.floatToIntBits(midVal); - int keyBits = Float.floatToIntBits(temperature); - if (midBits == keyBits) { // Values are equal - return mid; // Key found - } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN) - low = mid + 1; - } else { /* (0.0, -0.0) or (NaN, !NaN)*/ - high = mid - 1; - } - } - } - return -(low + 1); // key not found. - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java deleted file mode 100644 index 8ee5ef6badc3..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.systemui.statusbar.hvac; - -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft; - -import android.annotation.NonNull; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.RotateAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.TextSwitcher; - -/** - * Controls animating TemperatureView's text - */ -class TemperatureTextAnimator { - - private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = - new DecelerateInterpolator(); - private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR = - new AccelerateDecelerateInterpolator(); - - private static final int ROTATION_DEGREES = 15; - private static final int DURATION_MILLIS = 200; - - private AnimatedTemperatureView mParent; - private final TextSwitcher mTextSwitcher; - private final String mTempFormat; - private final int mPivotOffset; - private final CharSequence mMinText; - private final CharSequence mMaxText; - - private Animation mTextInAnimationUp; - private Animation mTextOutAnimationUp; - private Animation mTextInAnimationDown; - private Animation mTextOutAnimationDown; - private Animation mTextFadeInAnimation; - private Animation mTextFadeOutAnimation; - - private float mLastTemp = Float.NaN; - - TemperatureTextAnimator(AnimatedTemperatureView parent, TextSwitcher textSwitcher, - String tempFormat, int pivotOffset, - CharSequence minText, CharSequence maxText) { - mParent = parent; - mTextSwitcher = textSwitcher; - mTempFormat = tempFormat; - mPivotOffset = pivotOffset; - mMinText = minText; - mMaxText = maxText; - - mParent.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - setupAnimations(mParent.getGravity())); - } - - void setTemp(float temp) { - if (Float.isNaN(temp)) { - mTextSwitcher.setInAnimation(mTextFadeInAnimation); - mTextSwitcher.setOutAnimation(mTextFadeOutAnimation); - mTextSwitcher.setText("--"); - mLastTemp = temp; - return; - } - boolean isMinValue = mParent.isMinValue(temp); - boolean isMaxValue = mParent.isMaxValue(temp); - if (Float.isNaN(mLastTemp)) { - mTextSwitcher.setInAnimation(mTextFadeInAnimation); - mTextSwitcher.setOutAnimation(mTextFadeOutAnimation); - } else if (!isMinValue && (isMaxValue || temp > mLastTemp)) { - mTextSwitcher.setInAnimation(mTextInAnimationUp); - mTextSwitcher.setOutAnimation(mTextOutAnimationUp); - } else { - mTextSwitcher.setInAnimation(mTextInAnimationDown); - mTextSwitcher.setOutAnimation(mTextOutAnimationDown); - } - CharSequence text; - if (isMinValue) { - text = mMinText; - } else if (isMaxValue) { - text = mMaxText; - } else { - text = String.format(mTempFormat, temp); - } - mTextSwitcher.setText(text); - mLastTemp = temp; - } - - private void setupAnimations(int gravity) { - mTextFadeInAnimation = createFadeAnimation(true); - mTextFadeOutAnimation = createFadeAnimation(false); - if (!isHorizontal(gravity)) { - mTextInAnimationUp = createTranslateFadeAnimation(true, true); - mTextOutAnimationUp = createTranslateFadeAnimation(false, true); - mTextInAnimationDown = createTranslateFadeAnimation(true, false); - mTextOutAnimationDown = createTranslateFadeAnimation(false, false); - } else { - boolean isLeft = isLeft(gravity, mTextSwitcher.getLayoutDirection()); - mTextInAnimationUp = createRotateFadeAnimation(true, isLeft, true); - mTextOutAnimationUp = createRotateFadeAnimation(false, isLeft, true); - mTextInAnimationDown = createRotateFadeAnimation(true, isLeft, false); - mTextOutAnimationDown = createRotateFadeAnimation(false, isLeft, false); - } - } - - @NonNull - private Animation createFadeAnimation(boolean in) { - AnimationSet set = new AnimationSet(true); - AlphaAnimation alphaAnimation = new AlphaAnimation(in ? 0 : 1, in ? 1 : 0); - alphaAnimation.setDuration(DURATION_MILLIS); - set.addAnimation(new RotateAnimation(0, 0)); // Undo any previous rotation - set.addAnimation(alphaAnimation); - return set; - } - - @NonNull - private Animation createTranslateFadeAnimation(boolean in, boolean up) { - AnimationSet set = new AnimationSet(true); - set.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR); - set.setDuration(DURATION_MILLIS); - int fromYDelta = in ? (up ? 1 : -1) : 0; - int toYDelta = in ? 0 : (up ? -1 : 1); - set.addAnimation( - new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, fromYDelta, Animation.RELATIVE_TO_SELF, - toYDelta)); - set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0)); - return set; - } - - @NonNull - private Animation createRotateFadeAnimation(boolean in, boolean isLeft, boolean up) { - AnimationSet set = new AnimationSet(true); - set.setInterpolator(DECELERATE_INTERPOLATOR); - set.setDuration(DURATION_MILLIS); - - float degrees = isLeft == up ? -ROTATION_DEGREES : ROTATION_DEGREES; - int pivotX = isLeft ? -mPivotOffset : mParent.getWidth() + mPivotOffset; - set.addAnimation( - new RotateAnimation(in ? -degrees : 0f, in ? 0f : degrees, Animation.ABSOLUTE, - pivotX, Animation.ABSOLUTE, 0f)); - set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0)); - return set; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java deleted file mode 100644 index 2f79f960f951..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2020 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.voicerecognition.car; - -import android.bluetooth.BluetoothHeadsetClient; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.os.UserHandle; -import android.util.Log; -import android.widget.Toast; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; -import com.android.systemui.SysUIToast; -import com.android.systemui.SystemUI; -import com.android.systemui.dagger.qualifiers.Main; - -import javax.inject.Inject; - -/** - * Controller responsible for showing toast message when voice recognition over bluetooth device - * getting activated. - */ -public class ConnectedDeviceVoiceRecognitionNotifier extends SystemUI { - - private static final String TAG = "CarVoiceRecognition"; - @VisibleForTesting - static final int INVALID_VALUE = -1; - @VisibleForTesting - static final int VOICE_RECOGNITION_STARTED = 1; - - private Handler mHandler; - - private final BroadcastReceiver mVoiceRecognitionReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Voice recognition received an intent!"); - } - if (intent == null - || intent.getAction() == null - || !BluetoothHeadsetClient.ACTION_AG_EVENT.equals(intent.getAction()) - || !intent.hasExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION)) { - return; - } - - int voiceRecognitionState = intent.getIntExtra( - BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE); - - if (voiceRecognitionState == VOICE_RECOGNITION_STARTED) { - showToastMessage(); - } - } - }; - - private void showToastMessage() { - mHandler.post(() -> SysUIToast.makeText(mContext, R.string.voice_recognition_toast, - Toast.LENGTH_LONG).show()); - } - - @Inject - public ConnectedDeviceVoiceRecognitionNotifier(Context context, @Main Handler handler) { - super(context); - mHandler = handler; - } - - @Override - public void start() { - } - - @Override - protected void onBootCompleted() { - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT); - mContext.registerReceiverAsUser(mVoiceRecognitionReceiver, UserHandle.ALL, filter, - /* broadcastPermission= */ null, /* scheduler= */ null); - } -} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java new file mode 100644 index 000000000000..7996170ba7d6 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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.car.hvac; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.car.Car; +import android.car.hardware.hvac.CarHvacManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarServiceProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class HvacControllerTest extends SysuiTestCase { + + private static final int PROPERTY_ID = 1; + private static final int AREA_ID = 1; + private static final float VALUE = 72.0f; + + private HvacController mHvacController; + private CarServiceProvider mCarServiceProvider; + + @Mock + private Car mCar; + @Mock + private CarHvacManager mCarHvacManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mCar.isConnected()).thenReturn(true); + when(mCar.getCarManager(Car.HVAC_SERVICE)).thenReturn(mCarHvacManager); + + mCarServiceProvider = new CarServiceProvider(mContext, mCar); + mHvacController = new HvacController(mCarServiceProvider); + mHvacController.connectToCarService(); + } + + @Test + public void connectToCarService_registersCallback() { + verify(mCarHvacManager).registerCallback(any()); + } + + @Test + public void addTemperatureViewToController_usingTemperatureView_registersView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + + verify(v).setTemp(VALUE); + } + + @Test + public void addTemperatureViewToController_usingSameTemperatureView_registersFirstView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + resetTemperatureView(v, PROPERTY_ID, AREA_ID); + + mHvacController.addTemperatureViewToController(v); + verify(v, never()).setTemp(VALUE); + } + + @Test + public void addTemperatureViewToController_usingDifferentTemperatureView_registersBothViews() { + TemperatureTextView v1 = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v1); + verify(v1).setTemp(VALUE); + + TemperatureTextView v2 = setupMockTemperatureTextView( + PROPERTY_ID + 1, + AREA_ID + 1, + VALUE + 1); + mHvacController.addTemperatureViewToController(v2); + verify(v2).setTemp(VALUE + 1); + } + + @Test + public void removeAllComponents_ableToRegisterSameView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + + mHvacController.removeAllComponents(); + resetTemperatureView(v, PROPERTY_ID, AREA_ID); + + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + } + + private TemperatureTextView setupMockTemperatureTextView(int propertyId, int areaId, + float value) { + TemperatureTextView v = mock(TemperatureTextView.class); + resetTemperatureView(v, propertyId, areaId); + when(mCarHvacManager.isPropertyAvailable(propertyId, areaId)).thenReturn(true); + when(mCarHvacManager.getFloatProperty(propertyId, areaId)).thenReturn(value); + return v; + } + + private void resetTemperatureView(TemperatureTextView view, int propertyId, int areaId) { + reset(view); + when(view.getPropertyId()).thenReturn(propertyId); + when(view.getAreaId()).thenReturn(areaId); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java new file mode 100644 index 000000000000..80f3d1ee5dec --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 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.car.sideloaded; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarDeviceProvisionedController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class CarSideLoadedAppDetectorTest extends SysuiTestCase { + + private static final String SAFE_VENDOR = "com.safe.vendor"; + private static final String UNSAFE_VENDOR = "com.unsafe.vendor"; + private static final String APP_PACKAGE_NAME = "com.test"; + private static final String APP_CLASS_NAME = ".TestClass"; + + private CarSideLoadedAppDetector mSideLoadedAppDetector; + + @Mock + private PackageManager mPackageManager; + @Mock + private CarDeviceProvisionedController mCarDeviceProvisionedController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + TestableResources testableResources = mContext.getOrCreateTestableResources(); + String[] allowedAppInstallSources = new String[] {SAFE_VENDOR}; + testableResources.addOverride(R.array.config_allowedAppInstallSources, + allowedAppInstallSources); + + mSideLoadedAppDetector = new CarSideLoadedAppDetector(testableResources.getResources(), + mPackageManager, + mCarDeviceProvisionedController); + } + + @Test + public void isSafe_systemApp_returnsTrue() throws Exception { + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_updatedSystemApp_returnsTrue() throws Exception { + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + applicationInfo.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_nonSystemApp_withSafeSource_returnsTrue() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(SAFE_VENDOR, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); + } + + @Test + public void isSafe_nonSystemApp_withUnsafeSource_returnsFalse() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(UNSAFE_VENDOR, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); + } + + @Test + public void isSafe_nonSystemApp_withoutSource_returnsFalse() throws Exception { + InstallSourceInfo sourceInfo = new InstallSourceInfo(null, + /* initiatingPackageSigningInfo= */null, + /* originatingPackageName= */ null, + /* installingPackageName= */ null); + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = APP_PACKAGE_NAME; + + when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); + + assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java new file mode 100644 index 000000000000..eca51e34995c --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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.car.voicerecognition; + +import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE; +import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.bluetooth.BluetoothHeadsetClient; +import android.content.Intent; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { + + private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + + private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier; + private Handler mTestHandler; + + @Before + public void setUp() throws Exception { + TestableLooper testableLooper = TestableLooper.get(this); + mTestHandler = spy(new Handler(testableLooper.getLooper())); + mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier( + mContext, mTestHandler); + mVoiceRecognitionNotifier.onBootCompleted(); + } + + @Test + public void testReceiveIntent_started_showToast() { + Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); + intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, VOICE_RECOGNITION_STARTED); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + waitForIdleSync(); + + verify(mTestHandler).post(any()); + } + + @Test + public void testReceiveIntent_invalidExtra_noToast() { + Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); + intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + waitForIdleSync(); + + verify(mTestHandler, never()).post(any()); + } + + @Test + public void testReceiveIntent_noExtra_noToast() { + Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + waitForIdleSync(); + + verify(mTestHandler, never()).post(any()); + } + + @Test + public void testReceiveIntent_invalidIntent_noToast() { + Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + waitForIdleSync(); + + verify(mTestHandler, never()).post(any()); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java index bbcd0d4eff81..28c69c776f17 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java @@ -31,7 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.navigationbar.car.hvac.HvacController; +import com.android.systemui.car.hvac.HvacController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.phone.StatusBarIconController; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java deleted file mode 100644 index a71d1db3ee70..000000000000 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2019 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.navigationbar.car.hvac; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.car.Car; -import android.car.hardware.hvac.CarHvacManager; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.car.CarServiceProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -@SmallTest -public class HvacControllerTest extends SysuiTestCase { - - private static final int PROPERTY_ID = 1; - private static final int AREA_ID = 1; - private static final float VALUE = 72.0f; - - private HvacController mHvacController; - private CarServiceProvider mCarServiceProvider; - - @Mock - private Car mCar; - @Mock - private CarHvacManager mCarHvacManager; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mCar.isConnected()).thenReturn(true); - when(mCar.getCarManager(Car.HVAC_SERVICE)).thenReturn(mCarHvacManager); - - mCarServiceProvider = new CarServiceProvider(mContext, mCar); - mHvacController = new HvacController(mCarServiceProvider); - mHvacController.connectToCarService(); - } - - @Test - public void connectToCarService_registersCallback() { - verify(mCarHvacManager).registerCallback(any()); - } - - @Test - public void addTemperatureViewToController_usingTemperatureView_registersView() { - TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); - mHvacController.addTemperatureViewToController(v); - - verify(v).setTemp(VALUE); - } - - @Test - public void addTemperatureViewToController_usingSameTemperatureView_registersFirstView() { - TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); - mHvacController.addTemperatureViewToController(v); - verify(v).setTemp(VALUE); - resetTemperatureView(v, PROPERTY_ID, AREA_ID); - - mHvacController.addTemperatureViewToController(v); - verify(v, never()).setTemp(VALUE); - } - - @Test - public void addTemperatureViewToController_usingDifferentTemperatureView_registersBothViews() { - TemperatureTextView v1 = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); - mHvacController.addTemperatureViewToController(v1); - verify(v1).setTemp(VALUE); - - TemperatureTextView v2 = setupMockTemperatureTextView( - PROPERTY_ID + 1, - AREA_ID + 1, - VALUE + 1); - mHvacController.addTemperatureViewToController(v2); - verify(v2).setTemp(VALUE + 1); - } - - @Test - public void removeAllComponents_ableToRegisterSameView() { - TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); - mHvacController.addTemperatureViewToController(v); - verify(v).setTemp(VALUE); - - mHvacController.removeAllComponents(); - resetTemperatureView(v, PROPERTY_ID, AREA_ID); - - mHvacController.addTemperatureViewToController(v); - verify(v).setTemp(VALUE); - } - - private TemperatureTextView setupMockTemperatureTextView(int propertyId, int areaId, - float value) { - TemperatureTextView v = mock(TemperatureTextView.class); - resetTemperatureView(v, propertyId, areaId); - when(mCarHvacManager.isPropertyAvailable(propertyId, areaId)).thenReturn(true); - when(mCarHvacManager.getFloatProperty(propertyId, areaId)).thenReturn(value); - return v; - } - - private void resetTemperatureView(TemperatureTextView view, int propertyId, int areaId) { - reset(view); - when(view.getPropertyId()).thenReturn(propertyId); - when(view.getAreaId()).thenReturn(areaId); - } -} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java deleted file mode 100644 index aebb0e005019..000000000000 --- a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2020 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.sideloaded.car; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.pm.ApplicationInfo; -import android.content.pm.InstallSourceInfo; -import android.content.pm.PackageManager; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableResources; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.car.CarDeviceProvisionedController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -@SmallTest -public class CarSideLoadedAppDetectorTest extends SysuiTestCase { - - private static final String SAFE_VENDOR = "com.safe.vendor"; - private static final String UNSAFE_VENDOR = "com.unsafe.vendor"; - private static final String APP_PACKAGE_NAME = "com.test"; - private static final String APP_CLASS_NAME = ".TestClass"; - - private CarSideLoadedAppDetector mSideLoadedAppDetector; - - @Mock - private PackageManager mPackageManager; - @Mock - private CarDeviceProvisionedController mCarDeviceProvisionedController; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - TestableResources testableResources = mContext.getOrCreateTestableResources(); - String[] allowedAppInstallSources = new String[] {SAFE_VENDOR}; - testableResources.addOverride(R.array.config_allowedAppInstallSources, - allowedAppInstallSources); - - mSideLoadedAppDetector = new CarSideLoadedAppDetector(testableResources.getResources(), - mPackageManager, - mCarDeviceProvisionedController); - } - - @Test - public void isSafe_systemApp_returnsTrue() throws Exception { - ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); - stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = APP_PACKAGE_NAME; - applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - - when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) - .thenReturn(applicationInfo); - - assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); - } - - @Test - public void isSafe_updatedSystemApp_returnsTrue() throws Exception { - ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); - stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = APP_PACKAGE_NAME; - applicationInfo.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - - when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) - .thenReturn(applicationInfo); - - assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); - } - - @Test - public void isSafe_nonSystemApp_withSafeSource_returnsTrue() throws Exception { - InstallSourceInfo sourceInfo = new InstallSourceInfo(SAFE_VENDOR, - /* initiatingPackageSigningInfo= */null, - /* originatingPackageName= */ null, - /* installingPackageName= */ null); - ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); - stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = APP_PACKAGE_NAME; - - when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) - .thenReturn(applicationInfo); - when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); - - assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isTrue(); - } - - @Test - public void isSafe_nonSystemApp_withUnsafeSource_returnsFalse() throws Exception { - InstallSourceInfo sourceInfo = new InstallSourceInfo(UNSAFE_VENDOR, - /* initiatingPackageSigningInfo= */null, - /* originatingPackageName= */ null, - /* installingPackageName= */ null); - ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); - stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = APP_PACKAGE_NAME; - - when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) - .thenReturn(applicationInfo); - when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); - - assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); - } - - @Test - public void isSafe_nonSystemApp_withoutSource_returnsFalse() throws Exception { - InstallSourceInfo sourceInfo = new InstallSourceInfo(null, - /* initiatingPackageSigningInfo= */null, - /* originatingPackageName= */ null, - /* installingPackageName= */ null); - ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); - stackInfo.topActivity = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = APP_PACKAGE_NAME; - - when(mPackageManager.getApplicationInfoAsUser(eq(APP_PACKAGE_NAME), anyInt(), any())) - .thenReturn(applicationInfo); - when(mPackageManager.getInstallSourceInfo(APP_PACKAGE_NAME)).thenReturn(sourceInfo); - - assertThat(mSideLoadedAppDetector.isSafe(stackInfo)).isFalse(); - } -} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java deleted file mode 100644 index 38b47d0aea5d..000000000000 --- a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2019 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.voicerecognition.car; - -import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE; -import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.bluetooth.BluetoothHeadsetClient; -import android.content.Intent; -import android.os.Handler; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -@SmallTest -public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase { - - private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; - - private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier; - private Handler mTestHandler; - - @Before - public void setUp() throws Exception { - TestableLooper testableLooper = TestableLooper.get(this); - mTestHandler = spy(new Handler(testableLooper.getLooper())); - mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier( - mContext, mTestHandler); - mVoiceRecognitionNotifier.onBootCompleted(); - } - - @Test - public void testReceiveIntent_started_showToast() { - Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); - intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, VOICE_RECOGNITION_STARTED); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); - - verify(mTestHandler).post(any()); - } - - @Test - public void testReceiveIntent_invalidExtra_noToast() { - Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); - intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); - - verify(mTestHandler, never()).post(any()); - } - - @Test - public void testReceiveIntent_noExtra_noToast() { - Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); - - verify(mTestHandler, never()).post(any()); - } - - @Test - public void testReceiveIntent_invalidIntent_noToast() { - Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - waitForIdleSync(); - - verify(mTestHandler, never()).post(any()); - } -} -- cgit v1.2.3-59-g8ed1b