1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wallpaper.widget;
17 
18 import static android.view.View.MeasureSpec.EXACTLY;
19 import static android.view.View.MeasureSpec.makeMeasureSpec;
20 
21 import android.app.WallpaperColors;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.graphics.Point;
25 import android.text.format.DateFormat;
26 import android.view.Display;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.WindowManager;
31 import android.widget.TextView;
32 
33 import androidx.annotation.MainThread;
34 import androidx.annotation.Nullable;
35 import androidx.lifecycle.Lifecycle;
36 import androidx.lifecycle.LifecycleObserver;
37 import androidx.lifecycle.OnLifecycleEvent;
38 
39 import com.android.wallpaper.R;
40 import com.android.wallpaper.util.ScreenSizeCalculator;
41 import com.android.wallpaper.util.TimeUtils;
42 import com.android.wallpaper.util.TimeUtils.TimeTicker;
43 
44 import java.util.Calendar;
45 import java.util.Locale;
46 import java.util.TimeZone;
47 import java.util.concurrent.ExecutorService;
48 import java.util.concurrent.Executors;
49 
50 /** A class to load the new custom lockscreen view to the preview screen. */
51 public class LockScreenPreviewer implements LifecycleObserver {
52 
53     private static final String DEFAULT_DATE_PATTERN = "EEE, MMM d";
54     private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor();
55 
56     private final Lifecycle mLifecycle;
57     private final Context mContext;
58     private final String mDatePattern;
59     private final TextView mLockTime;
60     private final TextView mLockDate;
61     private TimeTicker mTicker;
62 
LockScreenPreviewer(Lifecycle lifecycle, Context context, ViewGroup previewContainer)63     public LockScreenPreviewer(Lifecycle lifecycle, Context context, ViewGroup previewContainer) {
64         mLifecycle = lifecycle;
65         mContext = context;
66         View contentView = LayoutInflater.from(mContext).inflate(
67                 R.layout.lock_screen_preview, /* root= */ null);
68         mLockTime = contentView.findViewById(R.id.lock_time);
69         mLockDate = contentView.findViewById(R.id.lock_date);
70         mDatePattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), DEFAULT_DATE_PATTERN);
71 
72         Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
73         Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay);
74 
75         Configuration config = mContext.getResources().getConfiguration();
76         boolean directionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
77 
78         View rootView = previewContainer.getRootView();
79         rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
80             @Override
81             public void onLayoutChange(View view, int left, int top, int right, int bottom,
82                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
83                 int cardHeight = previewContainer.getMeasuredHeight();
84                 int cardWidth = previewContainer.getMeasuredWidth();
85 
86                 // Relayout the content view to match full screen size.
87                 contentView.measure(
88                         makeMeasureSpec(screenSize.x, EXACTLY),
89                         makeMeasureSpec(screenSize.y, EXACTLY));
90                 contentView.layout(0, 0, screenSize.x, screenSize.y);
91 
92                 // Scale the content view from full screen size to the container(card) size.
93                 float scale = cardHeight > 0 ? (float) cardHeight / screenSize.y
94                         : (float) cardWidth / screenSize.x;
95                 contentView.setScaleX(scale);
96                 contentView.setScaleY(scale);
97                 // The pivot point is centered by default, set to (0, 0).
98                 contentView.setPivotX(directionLTR ? 0f : contentView.getMeasuredWidth());
99                 contentView.setPivotY(0f);
100 
101                 previewContainer.removeAllViews();
102                 previewContainer.addView(
103                         contentView,
104                         contentView.getMeasuredWidth(),
105                         contentView.getMeasuredHeight());
106                 rootView.removeOnLayoutChangeListener(this);
107             }
108         });
109         mLifecycle.addObserver(this);
110     }
111 
112     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
113     @MainThread
onResume()114     public void onResume() {
115         if (mTicker == null) {
116             sExecutorService.submit(() -> {
117                 if (mContext != null && mLifecycle.getCurrentState().isAtLeast(
118                         Lifecycle.State.RESUMED)) {
119                     mTicker = TimeTicker.registerNewReceiver(mContext, this::updateDateTime);
120                 }
121             });
122         }
123         updateDateTime();
124     }
125 
126     @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
127     @MainThread
onPause()128     public void onPause() {
129         unregisterReceiver();
130     }
131 
132     /**
133      * Sets the content's color based on the wallpaper's {@link WallpaperColors}.
134      *
135      * @param colors the {@link WallpaperColors} of the wallpaper which the lock screen overlay
136      *               will attach to, or {@code null} to use light color as default
137      */
setColor(@ullable WallpaperColors colors)138     public void setColor(@Nullable WallpaperColors colors) {
139         boolean useLightTextColor = colors == null
140                 || (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0;
141         int color = mContext.getColor(useLightTextColor
142                 ? R.color.text_color_light : R.color.text_color_dark);
143         int textShadowColor = mContext.getColor(useLightTextColor
144                 ? R.color.smartspace_preview_shadow_color_dark
145                 : R.color.smartspace_preview_shadow_color_transparent);
146         mLockDate.setTextColor(color);
147         mLockDate.setShadowLayer(
148                 mContext.getResources().getDimension(
149                         R.dimen.smartspace_preview_key_ambient_shadow_blur),
150                 /* dx = */ 0,
151                 /* dy = */ 0,
152                 textShadowColor);
153     }
154 
155     /** Sets visibility for date view. */
setDateViewVisibility(boolean visible)156     public void setDateViewVisibility(boolean visible) {
157         mLockDate.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
158     }
159 
release()160     public void release() {
161         mLifecycle.removeObserver(this);
162         unregisterReceiver();
163     }
164 
unregisterReceiver()165     private void unregisterReceiver() {
166         if (mTicker == null) {
167             return;
168         }
169 
170         sExecutorService.submit(() -> {
171             if (mContext != null && mTicker != null) {
172                 mContext.unregisterReceiver(mTicker);
173                 mTicker = null;
174             }
175         });
176     }
177 
updateDateTime()178     private void updateDateTime() {
179         Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
180         mLockDate.setText(DateFormat.format(mDatePattern, calendar));
181         mLockTime.setText(TimeUtils.getDoubleLineFormattedTime(mLockTime.getContext(), calendar));
182     }
183 }
184