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