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

Commit 906d65e2 authored by George Lin's avatar George Lin Committed by Android (Google) Code Review
Browse files

Merge "[TP] Clock Settings" into tm-qpr-dev

parents af546e7c b3e1934c
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
  ~ Copyright (C) 2023 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.
  ~
  -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/button_container_dynamic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/radio_button_dynamic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginEnd="8dp"
            android:clickable="false" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                style="@style/SectionTitleTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/clock_size_dynamic" />

            <TextView
                style="@style/SectionSubtitleTextStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/clock_size_dynamic_description" />
        </LinearLayout>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/button_container_large"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/radio_button_large"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginEnd="8dp"
            android:clickable="false" />

        <TextView
            style="@style/SectionTitleTextStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="@string/clock_size_large" />
    </LinearLayout>
</LinearLayout>
 No newline at end of file
+103 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
  ~ Copyright (C) 2023 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.
  ~
  -->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/section_header_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <include layout="@layout/section_header" />
    </FrameLayout>

    <com.android.wallpaper.picker.DisplayAspectRatioFrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:paddingTop="36dp"
        android:paddingBottom="40dp">

        <include
            android:id="@+id/preview"
            layout="@layout/wallpaper_preview_card"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="center"/>
    </com.android.wallpaper.picker.DisplayAspectRatioFrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginHorizontal="24dp"
        android:layout_marginBottom="28dp"
        android:background="@drawable/picker_fragment_background"
        android:paddingTop="22dp"
        android:paddingBottom="62dp">

        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/tabs"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clipToPadding="false"
                android:paddingHorizontal="16dp"
                android:layout_gravity="center_horizontal"/>

            <!--
            This is just an invisible placeholder put in place so that the parent keeps its height
            stable as the RecyclerView updates from 0 items to N items. Keeping it stable allows the
            layout logic to keep the size of the preview container stable as well, which bodes well
            for setting up the SurfaceView for remote rendering without changing its size after the
            content is loaded into the RecyclerView.

            It's critical for any TextViews inside the included layout to have text.
            -->
            <include
                layout="@layout/picker_fragment_tab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="invisible" />
        </FrameLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@id/affordances"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:clipToPadding="false"
                android:paddingHorizontal="16dp"
                android:visibility="gone"/>

            <com.android.customization.picker.clock.ui.view.ClockSizeRadioButtonGroup
                android:id="@+id/clock_size_radio_button_group"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingHorizontal="16dp" />
        </FrameLayout>
    </LinearLayout>
</LinearLayout>
+15 −0
Original line number Diff line number Diff line
@@ -36,6 +36,21 @@
    <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=15] -->
    <string name="clock_settings_title">Clock Settings</string>

    <!-- Title of a tab to change the clock color. [CHAR LIMIT=15] -->
    <string name="clock_color">Color</string>

    <!-- Title of a tab to change the clock size. [CHAR LIMIT=15] -->
    <string name="clock_size">Size</string>

    <!-- Title of a radio button to apply clock size dynamic. [CHAR LIMIT=15] -->
    <string name="clock_size_dynamic">Dynamic</string>

    <!-- Description of a radio button to apply clock size dynamic. [CHAR LIMIT=NONE] -->
    <string name="clock_size_dynamic_description">Clock size changes according to lock screen content</string>

    <!-- Title of a radio button to apply clock size large. [CHAR LIMIT=15] -->
    <string name="clock_size_large">Large</string>

    <!-- Title of a section of the customization picker where the user can select a Grid size for
        the home screen. [CHAR LIMIT=15] -->
    <string name="grid_title">App grid</string>
+0 −82
Original line number Diff line number Diff line
/*
 * 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.customization.picker.clock;

import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.customization.model.clock.BaseClockManager;
import com.android.customization.model.clock.Clockface;
import com.android.customization.model.clock.ContentProviderClockProvider;
import com.android.customization.picker.clock.ClockFragment.ClockFragmentHost;
import com.android.wallpaper.R;

/**
 * Activity allowing for the clock face picker to be linked to from other setup flows.
 *
 * This should be used with startActivityForResult. The resulting intent contains an extra
 * "clock_face_name" with the id of the picked clock face.
 */
public class ClockFacePickerActivity extends FragmentActivity implements ClockFragmentHost {

    private static final String EXTRA_CLOCK_FACE_NAME = "clock_face_name";

    private BaseClockManager mClockManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_clock_face_picker);

        // Creating a class that overrides {@link ClockManager#apply} to return the clock id to the
        // calling activity instead of putting the value into settings.
        //
        mClockManager = new BaseClockManager(
                new ContentProviderClockProvider(ClockFacePickerActivity.this)) {

            @Override
            protected void handleApply(Clockface option, Callback callback) {
                Intent result = new Intent();
                result.putExtra(EXTRA_CLOCK_FACE_NAME, option.getId());
                setResult(RESULT_OK, result);
                callback.onSuccess();
                finish();
            }

            @Override
            protected String lookUpCurrentClock() {
                return getIntent().getStringExtra(EXTRA_CLOCK_FACE_NAME);
            }
        };
        if (!mClockManager.isAvailable()) {
            finish();
        } else {
            final FragmentManager fm = getSupportFragmentManager();
            final FragmentTransaction fragmentTransaction = fm.beginTransaction();
            final ClockFragment clockFragment = ClockFragment.newInstance(
                    getString(R.string.clock_title));
            fragmentTransaction.replace(R.id.fragment_container, clockFragment);
            fragmentTransaction.commitNow();
        }
    }

    @Override
    public BaseClockManager getClockManager() {
        return mClockManager;
    }
}
+0 −209
Original line number Diff line number Diff line
/*
 * 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.customization.picker.clock;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.recyclerview.widget.RecyclerView;

import com.android.customization.model.CustomizationManager.Callback;
import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
import com.android.customization.model.clock.BaseClockManager;
import com.android.customization.model.clock.Clockface;
import com.android.customization.module.ThemesUserEventLogger;
import com.android.customization.picker.BasePreviewAdapter;
import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
import com.android.customization.widget.OptionSelectorController;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.Asset;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.picker.AppbarFragment;
import com.android.wallpaper.widget.PreviewPager;

import java.util.List;

/**
 * Fragment that contains the main UI for selecting and applying a Clockface.
 */
public class ClockFragment extends AppbarFragment {

    private static final String TAG = "ClockFragment";

    /**
     * Interface to be implemented by an Activity hosting a {@link ClockFragment}
     */
    public interface ClockFragmentHost {
        BaseClockManager getClockManager();
    }

    public static ClockFragment newInstance(CharSequence title) {
        ClockFragment fragment = new ClockFragment();
        fragment.setArguments(AppbarFragment.createArguments(title));
        return fragment;
    }

    private RecyclerView mOptionsContainer;
    private OptionSelectorController<Clockface> mOptionsController;
    private Clockface mSelectedOption;
    private BaseClockManager mClockManager;
    private PreviewPager mPreviewPager;
    private ContentLoadingProgressBar mLoading;
    private View mContent;
    private View mError;
    private ThemesUserEventLogger mEventLogger;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mClockManager = ((ClockFragmentHost) context).getClockManager();
        mEventLogger = (ThemesUserEventLogger)
                InjectorProvider.getInjector().getUserEventLogger(context);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(
                R.layout.fragment_clock_picker, container, /* attachToRoot */ false);
        setUpToolbar(view);
        mContent = view.findViewById(R.id.content_section);
        mPreviewPager = view.findViewById(R.id.clock_preview_pager);
        mOptionsContainer = view.findViewById(R.id.options_container);
        mLoading = view.findViewById(R.id.loading_indicator);
        mError = view.findViewById(R.id.error_section);
        setUpOptions();
        view.findViewById(R.id.apply_button).setOnClickListener(v -> {
            mClockManager.apply(mSelectedOption, new Callback() {
                @Override
                public void onSuccess() {
                    mOptionsController.setAppliedOption(mSelectedOption);
                    Toast.makeText(getContext(), R.string.applied_clock_msg,
                            Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onError(@Nullable Throwable throwable) {
                    if (throwable != null) {
                        Log.e(TAG, "Error loading clockfaces", throwable);
                    }
                    //TODO(santie): handle
                }
            });

        });
        return view;
    }

    private void createAdapter() {
        mPreviewPager.setAdapter(new ClockPreviewAdapter(getActivity(), mSelectedOption));
    }

    private void setUpOptions() {
        hideError();
        mLoading.show();
        mClockManager.fetchOptions(new OptionsFetchedListener<Clockface>() {
           @Override
           public void onOptionsLoaded(List<Clockface> options) {
               mLoading.hide();
               mOptionsController = new OptionSelectorController<>(mOptionsContainer, options);

               mOptionsController.addListener(selected -> {
                   mSelectedOption = (Clockface) selected;
                   mEventLogger.logClockSelected(mSelectedOption);
                   createAdapter();
               });
               mOptionsController.initOptions(mClockManager);
               for (Clockface option : options) {
                   if (option.isActive(mClockManager)) {
                       mSelectedOption = option;
                   }
               }
               // For development only, as there should always be a grid set.
               if (mSelectedOption == null) {
                   mSelectedOption = options.get(0);
               }
               createAdapter();
           }
           @Override
            public void onError(@Nullable Throwable throwable) {
                if (throwable != null) {
                   Log.e(TAG, "Error loading clockfaces", throwable);
                }
                showError();
            }
       }, false);
    }

    private void hideError() {
        mContent.setVisibility(View.VISIBLE);
        mError.setVisibility(View.GONE);
    }

    private void showError() {
        mLoading.hide();
        mContent.setVisibility(View.GONE);
        mError.setVisibility(View.VISIBLE);
    }

    private static class ClockfacePreviewPage extends PreviewPage {

        private final Asset mPreviewAsset;

        public ClockfacePreviewPage(String title, Activity activity, Asset previewAsset) {
            super(title, activity);
            mPreviewAsset = previewAsset;
        }

        @Override
        public void bindPreviewContent() {
            ImageView previewImage = card.findViewById(R.id.clock_preview_image);
            Context context = previewImage.getContext();
            Resources res = previewImage.getResources();
            mPreviewAsset.loadDrawableWithTransition(context, previewImage,
                    100 /* transitionDurationMillis */,
                    null /* drawableLoadedListener */,
                    res.getColor(android.R.color.transparent, null) /* placeholderColor */);
            card.setContentDescription(card.getResources().getString(
                    R.string.clock_preview_content_description, title));
        }
    }

    /**
     * Adapter class for mPreviewPager.
     * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
     * we don't want to just scroll)
     */
    private static class ClockPreviewAdapter extends BasePreviewAdapter<ClockfacePreviewPage> {
        ClockPreviewAdapter(Activity activity, Clockface clockface) {
            super(activity, R.layout.clock_preview_card);
            addPage(new ClockfacePreviewPage(
                    clockface.getTitle(), activity , clockface.getPreviewAsset()));
        }
    }
}
Loading