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

Commit 22f40b1e authored by Xiaowen Lei's avatar Xiaowen Lei Committed by Android (Google) Code Review
Browse files

Merge "Move Weather and AQI complications to vendor/unbundled_google." into tm-qpr-dev

parents eaa52a46 4a250127
Loading
Loading
Loading
Loading
+0 −70
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.dreams.complication;

import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_THRESHOLDS;
import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_VALUES;

import android.util.Log;

import androidx.annotation.ColorInt;

import javax.inject.Inject;
import javax.inject.Named;

final class AirQualityColorPicker {
    private static final String TAG = "AirQualityColorPicker";
    private final int[] mThresholds;
    private final int[] mColorValues;
    private final int mDefaultColor;

    @Inject
    AirQualityColorPicker(@Named(DREAM_AQI_COLOR_THRESHOLDS) int[] thresholds,
            @Named(DREAM_AQI_COLOR_VALUES) int[] colorValues,
            @Named(DREAM_AQI_COLOR_DEFAULT) @ColorInt int defaultColor) {
        mThresholds = thresholds;
        mColorValues = colorValues;
        mDefaultColor = defaultColor;
    }

    @ColorInt
    int getColorForValue(String aqiString) {
        int size = mThresholds.length;
        if (mThresholds.length != mColorValues.length) {
            size = Math.min(mThresholds.length, mColorValues.length);
            Log.e(TAG,
                    "Threshold size ("
                            + mThresholds.length + ") does not match color value size ("
                            + mColorValues.length
                            + "). Taking the minimum, some values may be ignored.");

        }
        try {
            final int value = Integer.parseInt(aqiString.replaceAll("[^0-9]", ""));
            for (int i = size - 1; i >= 0; i--) {
                if (value > mThresholds[i]) {
                    return mColorValues[i];
                }
            }
            Log.e(TAG, "No matching AQI color for value: " + value);
        } catch (NumberFormatException e) {
            Log.e(TAG, "Could not read AQI value from:" + aqiString);
        }
        return mDefaultColor;
    }
}
+0 −199
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.dreams.complication;

import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT;

import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.Nullable;

import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
import com.android.systemui.util.ViewController;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * Air quality complication which produces view holder responsible for showing AQI over dreams.
 */
public class DreamAirQualityComplication implements Complication {
    // TODO(b/236024839): Move to SmartspaceTarget
    public static final int FEATURE_AIR_QUALITY = 46;

    private final DreamAirQualityComplicationComponent.Factory mComponentFactory;

    @Inject
    public DreamAirQualityComplication(
            DreamAirQualityComplicationComponent.Factory componentFactory) {
        mComponentFactory = componentFactory;
    }

    @Override
    public int getRequiredTypeAvailability() {
        return COMPLICATION_TYPE_AIR_QUALITY;
    }

    @Override
    public ViewHolder createView(ComplicationViewModel model) {
        return mComponentFactory.create().getViewHolder();
    }

    /**
     * {@link CoreStartable} for registering {@link DreamAirQualityComplication} with SystemUI.
     */
    public static class Registrant extends CoreStartable {
        private final DreamOverlayStateController mDreamOverlayStateController;
        private final DreamAirQualityComplication mComplication;

        /**
         * Default constructor to register {@link DreamAirQualityComplication}.
         */
        @Inject
        public Registrant(Context context,
                DreamOverlayStateController dreamOverlayStateController,
                DreamAirQualityComplication complication) {
            super(context);
            mDreamOverlayStateController = dreamOverlayStateController;
            mComplication = complication;
        }

        @Override
        public void start() {
            // TODO(b/221500478): Only add complication once we have data to show.
            mDreamOverlayStateController.addComplication(mComplication);
        }
    }

    /**
     * ViewHolder to contain value/logic associated with the AQI complication view.
     */
    public static class DreamAirQualityViewHolder implements ViewHolder {
        private final TextView mView;
        private final DreamAirQualityViewController mController;
        private final ComplicationLayoutParams mLayoutParams;

        @Inject
        DreamAirQualityViewHolder(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view,
                DreamAirQualityViewController controller,
                @Named(DREAM_AQI_COMPLICATION_LAYOUT_PARAMS)
                        ComplicationLayoutParams layoutParams) {
            mView = view;
            mLayoutParams = layoutParams;
            mController = controller;
            mController.init();
        }

        @Override
        public View getView() {
            return mView;
        }

        @Override
        public ComplicationLayoutParams getLayoutParams() {
            return mLayoutParams;
        }
    }

    static class DreamAirQualityViewController extends ViewController<TextView> {
        private final DreamSmartspaceController mSmartspaceController;
        private final String mSmartspaceTrampolineComponent;
        private final ActivityStarter mActivityStarter;
        private final AirQualityColorPicker mAirQualityColorPicker;

        private final SmartspaceTargetListener mSmartspaceTargetListener = targets -> {
            final SmartspaceTarget target = targets.stream()
                    .filter(t -> t instanceof SmartspaceTarget)
                    .map(t -> (SmartspaceTarget) t)
                    .filter(t -> t.getFeatureType() == FEATURE_AIR_QUALITY)
                    .findFirst()
                    .orElse(null);
            updateView(target);
        };

        @Inject
        DreamAirQualityViewController(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view,
                DreamSmartspaceController smartspaceController,
                @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT)
                        String smartspaceTrampolineComponent,
                ActivityStarter activityStarter,
                AirQualityColorPicker airQualityColorPicker) {
            super(view);
            mSmartspaceController = smartspaceController;
            mSmartspaceTrampolineComponent = smartspaceTrampolineComponent;
            mActivityStarter = activityStarter;
            mAirQualityColorPicker = airQualityColorPicker;
        }

        @Override
        protected void onViewAttached() {
            mSmartspaceController.addUnfilteredListener(mSmartspaceTargetListener);
        }

        @Override
        protected void onViewDetached() {
            mSmartspaceController.removeUnfilteredListener(mSmartspaceTargetListener);
        }

        private void updateView(@Nullable SmartspaceTarget target) {
            final SmartspaceAction headerAction = target == null ? null : target.getHeaderAction();
            if (headerAction == null || TextUtils.isEmpty(headerAction.getTitle())) {
                mView.setVisibility(View.GONE);
                return;
            }
            mView.setVisibility(View.VISIBLE);

            final String airQuality = headerAction.getTitle().toString();
            mView.setText(airQuality);

            final Drawable background = mView.getBackground().mutate();
            final int color = mAirQualityColorPicker.getColorForValue(airQuality);

            if (background instanceof ShapeDrawable) {
                ((ShapeDrawable) background).getPaint().setColor(color);
            } else if (background instanceof GradientDrawable) {
                ((GradientDrawable) background).setColor(color);
            }
            mView.setBackground(background);

            final Intent intent = headerAction.getIntent();
            if (intent != null && intent.getComponent() != null
                    && intent.getComponent().getClassName().equals(
                    mSmartspaceTrampolineComponent)) {
                mView.setOnClickListener(v -> {
                    mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0);
                });
            }
        }
    }
}
+0 −221
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.dreams.complication;

import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT;

import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.widget.TextView;

import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
import com.android.systemui.util.ViewController;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * Weather Complication that produce Weather view holder.
 */
public class DreamWeatherComplication implements Complication {
    DreamWeatherComplicationComponent.Factory mComponentFactory;

    /**
     * Default constructor for {@link DreamWeatherComplication}.
     */
    @Inject
    public DreamWeatherComplication(
            DreamWeatherComplicationComponent.Factory componentFactory) {
        mComponentFactory = componentFactory;
    }

    @Override
    public int getRequiredTypeAvailability() {
        return COMPLICATION_TYPE_WEATHER;
    }

    /**
     * Create {@link DreamWeatherViewHolder}.
     */
    @Override
    public ViewHolder createView(ComplicationViewModel model) {
        return mComponentFactory.create().getViewHolder();
    }

    /**
     * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
     */
    public static class Registrant extends CoreStartable {
        private final DreamOverlayStateController mDreamOverlayStateController;
        private final DreamWeatherComplication mComplication;

        /**
         * Default constructor to register {@link DreamWeatherComplication}.
         */
        @Inject
        public Registrant(Context context,
                DreamOverlayStateController dreamOverlayStateController,
                DreamWeatherComplication dreamWeatherComplication) {
            super(context);
            mDreamOverlayStateController = dreamOverlayStateController;
            mComplication = dreamWeatherComplication;
        }

        @Override
        public void start() {
            mDreamOverlayStateController.addComplication(mComplication);
        }
    }

    /**
     * ViewHolder to contain value/logic associated with a Weather Complication View.
     */
    public static class DreamWeatherViewHolder implements ViewHolder {
        private final TextView mView;
        private final ComplicationLayoutParams mLayoutParams;
        private final DreamWeatherViewController mViewController;

        @Inject
        DreamWeatherViewHolder(
                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
                DreamWeatherViewController controller,
                @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
                        ComplicationLayoutParams layoutParams) {
            mView = view;
            mLayoutParams = layoutParams;
            mViewController = controller;
            mViewController.init();
        }

        @Override
        public TextView getView() {
            return mView;
        }

        @Override
        public ComplicationLayoutParams getLayoutParams() {
            return mLayoutParams;
        }
    }

    /**
     * ViewController to contain value/logic associated with a Weather Complication View.
     */
    static class DreamWeatherViewController extends ViewController<TextView> {
        private final DreamSmartspaceController mSmartSpaceController;
        private final ActivityStarter mActivityStarter;
        private final String mSmartspaceTrampolineActivityComponent;
        private SmartspaceTargetListener mSmartspaceTargetListener;
        private final Resources mResources;

        @Inject
        DreamWeatherViewController(
                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
                @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT) String smartspaceTrampoline,
                ActivityStarter activityStarter,
                DreamSmartspaceController smartspaceController,
                Resources resources
        ) {
            super(view);
            mActivityStarter = activityStarter;
            mResources = resources;
            mSmartSpaceController = smartspaceController;
            mSmartspaceTrampolineActivityComponent = smartspaceTrampoline;
        }

        @Override
        protected void onViewAttached() {
            mSmartspaceTargetListener = targets -> targets.forEach(
                    t -> {
                        if (t instanceof SmartspaceTarget
                                && ((SmartspaceTarget) t).getFeatureType()
                                == SmartspaceTarget.FEATURE_WEATHER) {
                            final SmartspaceTarget target = (SmartspaceTarget) t;
                            final SmartspaceAction headerAction = target.getHeaderAction();
                            if (headerAction == null || TextUtils.isEmpty(
                                    headerAction.getTitle())) {
                                return;
                            }

                            final CharSequence temperature = headerAction.getTitle();
                            mView.setText(temperature);
                            mView.setContentDescription(getFormattedContentDescription(temperature,
                                    headerAction.getContentDescription()));
                            final Icon icon = headerAction.getIcon();
                            if (icon != null) {
                                final int iconSize =
                                        getResources().getDimensionPixelSize(
                                                R.dimen.smart_action_button_icon_size);
                                final Drawable iconDrawable = icon.loadDrawable(getContext());
                                iconDrawable.setBounds(0, 0, iconSize, iconSize);
                                mView.setCompoundDrawables(iconDrawable, null, null, null);
                                mView.setCompoundDrawablePadding(
                                        getResources().getDimensionPixelSize(
                                                R.dimen.smart_action_button_icon_padding));
                            }
                            mView.setOnClickListener(v -> {
                                final Intent intent = headerAction.getIntent();
                                if (intent != null && intent.getComponent() != null
                                        && intent.getComponent().getClassName()
                                        .equals(mSmartspaceTrampolineActivityComponent)) {
                                    mActivityStarter.postStartActivityDismissingKeyguard(
                                            intent, 0 /*delay*/);
                                }
                            });
                        }
                    });
            // We need to use an unfiltered listener here since weather is filtered from showing
            // in the dream smartspace.
            mSmartSpaceController.addUnfilteredListener(mSmartspaceTargetListener);
        }

        @Override
        protected void onViewDetached() {
            mSmartSpaceController.removeUnfilteredListener(mSmartspaceTargetListener);
        }

        /**
         * Returns a formatted content description for accessibility of the weather condition and
         * temperature.
         */
        private CharSequence getFormattedContentDescription(CharSequence temperature,
                CharSequence weatherCondition) {
            if (TextUtils.isEmpty(temperature)) {
                return weatherCondition;
            } else if (TextUtils.isEmpty(weatherCondition)) {
                return temperature;
            }

            return mResources.getString(R.string.dream_overlay_weather_complication_desc,
                    weatherCondition, temperature);
        }
    }
}
+0 −137

File deleted.

Preview size limit exceeded, changes collapsed.

+0 −120
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.dreams.complication.dagger;


import static java.lang.annotation.RetentionPolicy.RUNTIME;

import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;

import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

import javax.inject.Named;
import javax.inject.Scope;

import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.Subcomponent;

/**
 * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
 * the
 * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
 * details.
 */
@Subcomponent(modules = {
        DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
})
@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
public interface DreamWeatherComplicationComponent {
    /**
     * Creates {@link DreamWeatherViewHolder}.
     */
    DreamWeatherViewHolder getViewHolder();

    @Documented
    @Retention(RUNTIME)
    @Scope
    @interface DreamWeatherComplicationScope {
    }

    /**
     * Generates {@link DreamWeatherComplicationComponent}.
     */
    @Subcomponent.Factory
    interface Factory {
        DreamWeatherComplicationComponent create();
    }

    /**
     * Scoped values for {@link DreamWeatherComplicationComponent}.
     */
    @Module
    interface DreamWeatherComplicationModule {
        String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
        String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
                "weather_complication_layout_params";
        // Order weight of insert into parent container
        int INSERT_ORDER_WEIGHT = 2;

        /**
         * Provides the complication view.
         */
        @Provides
        @DreamWeatherComplicationScope
        @Named(DREAM_WEATHER_COMPLICATION_VIEW)
        static TextView provideComplicationView(LayoutInflater layoutInflater) {
            return Preconditions.checkNotNull((TextView)
                            layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
                                    null, false),
                    "R.layout.dream_overlay_complication_weather did not properly inflated");
        }

        /**
         * Provides the layout parameters for the complication view.
         */
        @Provides
        @DreamWeatherComplicationScope
        @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
        static ComplicationLayoutParams provideLayoutParams() {
            return new ComplicationLayoutParams(0,
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ComplicationLayoutParams.POSITION_BOTTOM
                            | ComplicationLayoutParams.POSITION_START,
                    ComplicationLayoutParams.DIRECTION_END,
                    INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
        }

        /**
         * Binds resources in the dream weather complication scope.
         */
        @Binds
        @DreamWeatherComplicationScope
        Resources getResources(@Main Resources resources);
    }
}
Loading