blob: 360b6fd3177efda744635c7c4c70d15b4519a3c9 [file] [log] [blame]
/*
* Copyright (C) 2021 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.wallpaper.widget;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import android.app.WallpaperColors;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.text.SpannableString;
import android.text.format.DateFormat;
import android.text.style.ReplacementSpan;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.wallpaper.R;
import com.android.wallpaper.util.ScreenSizeCalculator;
import com.android.wallpaper.util.TimeUtils;
import com.android.wallpaper.util.TimeUtils.TimeTicker;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Executors;
/** A class to load the new custom lockscreen view to the preview screen. */
public class LockScreenPreviewer2 implements LifecycleObserver {
private static final String DEFAULT_DATE_PATTERN = "EEE, MMM d";
private final Lifecycle mLifecycle;
private final Context mContext;
private final String mDatePattern;
private final TextView mLockTime;
private final TextView mLockDate;
private TimeTicker mTicker;
public LockScreenPreviewer2(Lifecycle lifecycle, Context context, ViewGroup previewContainer) {
mLifecycle = lifecycle;
mContext = context;
View contentView = LayoutInflater.from(mContext).inflate(
R.layout.lock_screen_preview2, /* root= */ null);
mLockTime = contentView.findViewById(R.id.lock_time);
mLockDate = contentView.findViewById(R.id.lock_date);
mDatePattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), DEFAULT_DATE_PATTERN);
Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(defaultDisplay);
Configuration config = mContext.getResources().getConfiguration();
boolean directionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
View rootView = previewContainer.getRootView();
rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
int cardHeight = previewContainer.getMeasuredHeight();
int cardWidth = previewContainer.getMeasuredWidth();
// Relayout the content view to match full screen size.
contentView.measure(
makeMeasureSpec(screenSize.x, EXACTLY),
makeMeasureSpec(screenSize.y, EXACTLY));
contentView.layout(0, 0, screenSize.x, screenSize.y);
// Scale the content view from full screen size to the container(card) size.
float scale = cardHeight > 0 ? (float) cardHeight / screenSize.y
: (float) cardWidth / screenSize.x;
contentView.setScaleX(scale);
contentView.setScaleY(scale);
// The pivot point is centered by default, set to (0, 0).
contentView.setPivotX(directionLTR ? 0f : contentView.getMeasuredWidth());
contentView.setPivotY(0f);
previewContainer.removeAllViews();
previewContainer.addView(
contentView,
contentView.getMeasuredWidth(),
contentView.getMeasuredHeight());
rootView.removeOnLayoutChangeListener(this);
}
});
mLifecycle.addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@MainThread
public void onResume() {
if (mContext != null) {
Executors.newSingleThreadExecutor().submit(() -> {
if (mContext != null && mLifecycle.getCurrentState().isAtLeast(
Lifecycle.State.RESUMED)) {
mTicker = TimeTicker.registerNewReceiver(mContext, this::updateDateTime);
}
});
updateDateTime();
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@MainThread
public void onPause() {
if (mContext != null && mTicker != null) {
mContext.unregisterReceiver(mTicker);
}
}
/**
* Sets the content's color based on the wallpaper's {@link WallpaperColors}.
*
* @param colors the {@link WallpaperColors} of the wallpaper which the lock screen overlay
* will attach to, or {@code null} to use light color as default
*/
public void setColor(@Nullable WallpaperColors colors) {
boolean useLightTextColor = colors == null
|| (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0;
int color = mContext.getColor(useLightTextColor
? R.color.text_color_light : R.color.text_color_dark);
int textShadowColor = mContext.getColor(useLightTextColor
? R.color.smartspace_preview_shadow_color_dark
: R.color.smartspace_preview_shadow_color_transparent);
mLockDate.setTextColor(color);
mLockDate.setShadowLayer(
mContext.getResources().getDimension(
R.dimen.smartspace_preview_key_ambient_shadow_blur),
/* dx = */ 0,
/* dy = */ 0,
textShadowColor);
}
public void release() {
mLifecycle.removeObserver(this);
}
private void updateDateTime() {
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
mLockDate.setText(DateFormat.format(mDatePattern, calendar));
SpannableString timeWithMonospaceText = new SpannableString(
TimeUtils.getDoubleLineFormattedTime(mContext, calendar));
timeWithMonospaceText.setSpan(new MonospaceSpan(), /* start= */ 0,
timeWithMonospaceText.length(), /* flag= */ 0);
mLockTime.setText(timeWithMonospaceText);
}
/** Make text monospace without overriding the text fontFamily. */
private static class MonospaceSpan extends ReplacementSpan {
@Override
public int getSize(@NonNull Paint paint, @NonNull CharSequence text, int start, int end,
@Nullable Paint.FontMetricsInt fontMetricsInt) {
if (fontMetricsInt != null) {
paint.getFontMetricsInt(fontMetricsInt);
}
int count = end - start;
if (text.charAt(start) == '\n') {
count--;
}
if (text.charAt(end - 1) == '\n') {
count--;
}
return getMaxCharWidth(paint, text, /* start= */ 0, text.length())
* Math.max(count, 0);
}
@Override
public void draw(@NonNull Canvas canvas, @NonNull CharSequence text, int start, int end,
float x, int top, int y, int bottom, @NonNull Paint paint) {
float[] widths = new float[end - start];
paint.getTextWidths(text, start, end, widths);
int maxCharWidth = getMaxCharWidth(paint, text, /* start= */ 0, text.length());
for (int i = 0; i < end - start; ++i) {
canvas.drawText(text, start + i, start + i + 1,
x + maxCharWidth * i + (maxCharWidth - widths[i]) / 2, y, paint);
}
}
private int getMaxCharWidth(Paint paint, CharSequence text, int start, int end) {
float[] widths = new float[end - start];
paint.getTextWidths(text, start, end, widths);
float max = 0;
for (float w : widths) {
if (max < w) {
max = w;
}
}
return Math.round(max);
}
}
}