Remove deprecated clock components

Bug: 281052188
Test: Rebuilt device image
Change-Id: If83e55c52fc1d62087eb20ec35db9a9d74c45b1e
This commit is contained in:
Hawkwood Glazier
2023-06-14 15:57:30 +00:00
parent a832ada6f4
commit f1b27ea81a
31 changed files with 2 additions and 3263 deletions

View File

@@ -1067,15 +1067,6 @@
android:exported="true">
</provider>
<!-- Provides list and realistic previews of clock faces for the picker app. -->
<provider
android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.keyguard.clock"
android:enabled="false"
android:exported="false"
android:grantUriPermissions="true">
</provider>
<receiver
android:name=".statusbar.KeyboardShortcutsReceiver"
android:exported="true">

View File

@@ -40,11 +40,6 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.systemui.test.keyguard.clock.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.systemui.people.PeopleProvider"
android:authorities="com.android.systemui.test.people.disabled"
android:enabled="false"

View File

@@ -1,7 +1,7 @@
# Clock Plugins
The clock appearing on the lock screen and always on display (AOD) can be
customized via the ClockProviderPlugin plugin interface.
The clock appearing on the lock screen and always on display (AOD) can be customized via the
ClockProviderPlugin plugin interface. The ClockPlugin interface has been removed.
## Lock screen integration
The lockscreen code has two main components, a [clock customization library](../customization), and

View File

@@ -1,145 +0,0 @@
/*
* Copyright (C) 2018 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.plugins;
import android.graphics.Bitmap;
import android.graphics.Paint.Style;
import android.view.View;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import java.util.TimeZone;
/**
* Plugin used to replace main clock in keyguard.
* @deprecated Migrating to ClockProviderPlugin
*/
@Deprecated
@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
public interface ClockPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_CLOCK";
int VERSION = 5;
/**
* Get the name of the clock face.
*
* This name should not be translated.
*/
String getName();
/**
* Get the title of the clock face to be shown in the picker app.
*/
String getTitle();
/**
* Get thumbnail of clock face to be shown in the picker app.
*/
Bitmap getThumbnail();
/**
* Get preview images of clock face to be shown in the picker app.
*
* Preview image should be realistic and show what the clock face will look like on AOD and lock
* screen.
*
* @param width width of the preview image, should be the same as device width in pixels.
* @param height height of the preview image, should be the same as device height in pixels.
*/
Bitmap getPreview(int width, int height);
/**
* Get clock view.
* @return clock view from plugin.
*/
View getView();
/**
* Get clock view for a large clock that appears behind NSSL.
*/
default View getBigClockView() {
return null;
}
/**
* Returns the preferred Y position of the clock.
*
* @param totalHeight Height of the parent container.
* @return preferred Y position.
*/
int getPreferredY(int totalHeight);
/**
* Allows the plugin to clean up resources when no longer needed.
*
* Called when the view previously created by {@link ClockPlugin#getView()} has been detached
* from the view hierarchy.
*/
void onDestroyView();
/**
* Set clock paint style.
* @param style The new style to set in the paint.
*/
void setStyle(Style style);
/**
* Set clock text color.
* @param color A color value.
*/
void setTextColor(int color);
/**
* Sets the color palette for the clock face.
* @param supportsDarkText Whether dark text can be displayed.
* @param colors Colors that should be used on the clock face, ordered from darker to lighter.
*/
default void setColorPalette(boolean supportsDarkText, int[] colors) {}
/**
* Set the amount (ratio) that the device has transitioned to doze.
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
default void setDarkAmount(float darkAmount) {}
/**
* Notifies that time tick alarm from doze service fired.
*
* Implement this method instead of registering a broadcast listener for TIME_TICK.
*/
default void onTimeTick() {}
/**
* Notifies that the time zone has changed.
*
* Implement this method instead of registering a broadcast listener for TIME_ZONE_CHANGED.
*/
default void onTimeZoneChanged(TimeZone timeZone) {}
/**
* Notifies that the time format has changed.
*
* @param timeFormat "12" for 12-hour format, "24" for 24-hour format
*/
default void onTimeFormatChanged(String timeFormat) {}
/**
* Indicates whether the keyguard status area (date) should be shown below
* the clock.
*/
default boolean shouldShowStatusArea() {
return true;
}
}

View File

@@ -1,210 +0,0 @@
/*
* 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.keyguard.clock;
import android.app.WallpaperManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint.Style;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextClock;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ClockPlugin;
import java.util.TimeZone;
/**
* Controller for Stretch clock that can appear on lock screen and AOD.
*/
public class AnalogClockController implements ClockPlugin {
/**
* Resources used to get title and thumbnail.
*/
private final Resources mResources;
/**
* LayoutInflater used to inflate custom clock views.
*/
private final LayoutInflater mLayoutInflater;
/**
* Extracts accent color from wallpaper.
*/
private final SysuiColorExtractor mColorExtractor;
/**
* Computes preferred position of clock.
*/
private final SmallClockPosition mClockPosition;
/**
* Renders preview from clock view.
*/
private final ViewPreviewer mRenderer = new ViewPreviewer();
/**
* Custom clock shown on AOD screen and behind stack scroller on lock.
*/
private ClockLayout mBigClockView;
private ImageClock mAnalogClock;
/**
* Small clock shown on lock screen above stack scroller.
*/
private View mView;
private TextClock mLockClock;
/**
* Helper to extract colors from wallpaper palette for clock face.
*/
private final ClockPalette mPalette = new ClockPalette();
/**
* Create a BubbleClockController instance.
*
* @param res Resources contains title and thumbnail.
* @param inflater Inflater used to inflate custom clock views.
* @param colorExtractor Extracts accent color from wallpaper.
*/
public AnalogClockController(Resources res, LayoutInflater inflater,
SysuiColorExtractor colorExtractor) {
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
mClockPosition = new SmallClockPosition(inflater.getContext());
}
private void createViews() {
mBigClockView = (ClockLayout) mLayoutInflater.inflate(R.layout.analog_clock, null);
mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
mView = mLayoutInflater.inflate(R.layout.digital_clock, null);
mLockClock = mView.findViewById(R.id.lock_screen_clock);
}
@Override
public void onDestroyView() {
mBigClockView = null;
mAnalogClock = null;
mView = null;
mLockClock = null;
}
@Override
public String getName() {
return "analog";
}
@Override
public String getTitle() {
return mResources.getString(R.string.clock_title_analog);
}
@Override
public Bitmap getThumbnail() {
return BitmapFactory.decodeResource(mResources, R.drawable.analog_thumbnail);
}
@Override
public Bitmap getPreview(int width, int height) {
// Use the big clock view for the preview
View view = getBigClockView();
// Initialize state of plugin before generating preview.
setDarkAmount(1f);
setTextColor(Color.WHITE);
ColorExtractor.GradientColors colors = mColorExtractor.getColors(
WallpaperManager.FLAG_LOCK);
setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
onTimeTick();
return mRenderer.createPreview(view, width, height);
}
@Override
public View getView() {
if (mView == null) {
createViews();
}
return mView;
}
@Override
public View getBigClockView() {
if (mBigClockView == null) {
createViews();
}
return mBigClockView;
}
@Override
public int getPreferredY(int totalHeight) {
return mClockPosition.getPreferredY();
}
@Override
public void setStyle(Style style) {}
@Override
public void setTextColor(int color) {
updateColor();
}
@Override
public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
mPalette.setColorPalette(supportsDarkText, colorPalette);
updateColor();
}
private void updateColor() {
final int primary = mPalette.getPrimaryColor();
final int secondary = mPalette.getSecondaryColor();
mLockClock.setTextColor(secondary);
mAnalogClock.setClockColors(primary, secondary);
}
@Override
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mBigClockView.onTimeChanged();
mLockClock.refreshTime();
}
@Override
public void setDarkAmount(float darkAmount) {
mPalette.setDarkAmount(darkAmount);
mClockPosition.setDarkAmount(darkAmount);
mBigClockView.setDarkAmount(darkAmount);
}
@Override
public void onTimeZoneChanged(TimeZone timeZone) {
mAnalogClock.onTimeZoneChanged(timeZone);
}
@Override
public boolean shouldShowStatusArea() {
return true;
}
}

View File

@@ -1,210 +0,0 @@
/*
* 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.keyguard.clock;
import android.app.WallpaperManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint.Style;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextClock;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ClockPlugin;
import java.util.TimeZone;
/**
* Controller for Bubble clock that can appear on lock screen and AOD.
*/
public class BubbleClockController implements ClockPlugin {
/**
* Resources used to get title and thumbnail.
*/
private final Resources mResources;
/**
* LayoutInflater used to inflate custom clock views.
*/
private final LayoutInflater mLayoutInflater;
/**
* Extracts accent color from wallpaper.
*/
private final SysuiColorExtractor mColorExtractor;
/**
* Computes preferred position of clock.
*/
private final SmallClockPosition mClockPosition;
/**
* Renders preview from clock view.
*/
private final ViewPreviewer mRenderer = new ViewPreviewer();
/**
* Custom clock shown on AOD screen and behind stack scroller on lock.
*/
private ClockLayout mView;
private ImageClock mAnalogClock;
/**
* Small clock shown on lock screen above stack scroller.
*/
private View mLockClockContainer;
private TextClock mLockClock;
/**
* Helper to extract colors from wallpaper palette for clock face.
*/
private final ClockPalette mPalette = new ClockPalette();
/**
* Create a BubbleClockController instance.
*
* @param res Resources contains title and thumbnail.
* @param inflater Inflater used to inflate custom clock views.
* @param colorExtractor Extracts accent color from wallpaper.
*/
public BubbleClockController(Resources res, LayoutInflater inflater,
SysuiColorExtractor colorExtractor) {
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
mClockPosition = new SmallClockPosition(inflater.getContext());
}
private void createViews() {
mView = (ClockLayout) mLayoutInflater.inflate(R.layout.bubble_clock, null);
mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
mLockClockContainer = mLayoutInflater.inflate(R.layout.digital_clock, null);
mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
}
@Override
public void onDestroyView() {
mView = null;
mAnalogClock = null;
mLockClockContainer = null;
mLockClock = null;
}
@Override
public String getName() {
return "bubble";
}
@Override
public String getTitle() {
return mResources.getString(R.string.clock_title_bubble);
}
@Override
public Bitmap getThumbnail() {
return BitmapFactory.decodeResource(mResources, R.drawable.bubble_thumbnail);
}
@Override
public Bitmap getPreview(int width, int height) {
// Use the big clock view for the preview
View view = getBigClockView();
// Initialize state of plugin before generating preview.
setDarkAmount(1f);
setTextColor(Color.WHITE);
ColorExtractor.GradientColors colors = mColorExtractor.getColors(
WallpaperManager.FLAG_LOCK);
setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
onTimeTick();
return mRenderer.createPreview(view, width, height);
}
@Override
public View getView() {
if (mLockClockContainer == null) {
createViews();
}
return mLockClockContainer;
}
@Override
public View getBigClockView() {
if (mView == null) {
createViews();
}
return mView;
}
@Override
public int getPreferredY(int totalHeight) {
return mClockPosition.getPreferredY();
}
@Override
public void setStyle(Style style) {}
@Override
public void setTextColor(int color) {
updateColor();
}
@Override
public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
mPalette.setColorPalette(supportsDarkText, colorPalette);
updateColor();
}
private void updateColor() {
final int primary = mPalette.getPrimaryColor();
final int secondary = mPalette.getSecondaryColor();
mLockClock.setTextColor(secondary);
mAnalogClock.setClockColors(primary, secondary);
}
@Override
public void setDarkAmount(float darkAmount) {
mPalette.setDarkAmount(darkAmount);
mClockPosition.setDarkAmount(darkAmount);
mView.setDarkAmount(darkAmount);
}
@Override
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mView.onTimeChanged();
mLockClock.refreshTime();
}
@Override
public void onTimeZoneChanged(TimeZone timeZone) {
mAnalogClock.onTimeZoneChanged(timeZone);
}
@Override
public boolean shouldShowStatusArea() {
return true;
}
}

View File

@@ -1,117 +0,0 @@
/*
* 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.keyguard.clock;
import android.graphics.Bitmap;
import java.util.function.Supplier;
/**
* Metadata about an available clock face.
*/
final class ClockInfo {
private final String mName;
private final Supplier<String> mTitle;
private final String mId;
private final Supplier<Bitmap> mThumbnail;
private final Supplier<Bitmap> mPreview;
private ClockInfo(String name, Supplier<String> title, String id,
Supplier<Bitmap> thumbnail, Supplier<Bitmap> preview) {
mName = name;
mTitle = title;
mId = id;
mThumbnail = thumbnail;
mPreview = preview;
}
/**
* Gets the non-internationalized name for the clock face.
*/
String getName() {
return mName;
}
/**
* Gets the name (title) of the clock face to be shown in the picker app.
*/
String getTitle() {
return mTitle.get();
}
/**
* Gets the ID of the clock face, used by the picker to set the current selection.
*/
String getId() {
return mId;
}
/**
* Gets a thumbnail image of the clock.
*/
Bitmap getThumbnail() {
return mThumbnail.get();
}
/**
* Gets a potentially realistic preview image of the clock face.
*/
Bitmap getPreview() {
return mPreview.get();
}
static Builder builder() {
return new Builder();
}
static class Builder {
private String mName;
private Supplier<String> mTitle;
private String mId;
private Supplier<Bitmap> mThumbnail;
private Supplier<Bitmap> mPreview;
public ClockInfo build() {
return new ClockInfo(mName, mTitle, mId, mThumbnail, mPreview);
}
public Builder setName(String name) {
mName = name;
return this;
}
public Builder setTitle(Supplier<String> title) {
mTitle = title;
return this;
}
public Builder setId(String id) {
mId = id;
return this;
}
public Builder setThumbnail(Supplier<Bitmap> thumbnail) {
mThumbnail = thumbnail;
return this;
}
public Builder setPreview(Supplier<Bitmap> preview) {
mPreview = preview;
return this;
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* 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.keyguard.clock;
import java.util.List;
import dagger.Module;
import dagger.Provides;
/**
* Dagger Module for clock package.
*
* @deprecated Migrate to ClockRegistry
*/
@Module
@Deprecated
public abstract class ClockInfoModule {
/** */
@Provides
public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
return clockManager.getClockInfos();
}
}

View File

@@ -1,109 +0,0 @@
/*
* 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.keyguard.clock;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
/**
* Positions clock faces (analog, digital, typographic) and handles pixel shifting
* to prevent screen burn-in.
*/
public class ClockLayout extends FrameLayout {
private static final int ANALOG_CLOCK_SHIFT_FACTOR = 3;
/**
* Clock face views.
*/
private View mAnalogClock;
/**
* Pixel shifting amplitudes used to prevent screen burn-in.
*/
private int mBurnInPreventionOffsetX;
private int mBurnInPreventionOffsetY;
private float mDarkAmount;
public ClockLayout(Context context) {
this(context, null);
}
public ClockLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mAnalogClock = findViewById(R.id.analog_clock);
// Get pixel shifting X, Y amplitudes from resources.
Resources resources = getResources();
mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
positionChildren();
}
void onTimeChanged() {
positionChildren();
}
/**
* See {@link com.android.systemui.plugins.ClockPlugin#setDarkAmount(float)}.
*/
void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
positionChildren();
}
private void positionChildren() {
final float offsetX = MathUtils.lerp(0f,
getBurnInOffset(mBurnInPreventionOffsetX * 2, true) - mBurnInPreventionOffsetX,
mDarkAmount);
final float offsetY = MathUtils.lerp(0f,
getBurnInOffset(mBurnInPreventionOffsetY * 2, false)
- 0.5f * mBurnInPreventionOffsetY,
mDarkAmount);
// Put the analog clock in the middle of the screen.
if (mAnalogClock != null) {
mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
+ ANALOG_CLOCK_SHIFT_FACTOR * offsetX);
mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
+ ANALOG_CLOCK_SHIFT_FACTOR * offsetY);
}
}
}

View File

@@ -1,392 +0,0 @@
/*
* 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.keyguard.clock;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManager.DockEventListener;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
/**
* Manages custom clock faces for AOD and lock screen.
*
* @deprecated Migrate to ClockRegistry
*/
@SysUISingleton
@Deprecated
public final class ClockManager {
private static final String TAG = "ClockOptsProvider";
private final AvailableClocks mPreviewClocks;
private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
private final Context mContext;
private final ContentResolver mContentResolver;
private final SettingsWrapper mSettingsWrapper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final UserTracker mUserTracker;
private final Executor mMainExecutor;
/**
* Observe settings changes to know when to switch the clock face.
*/
private final ContentObserver mContentObserver =
new ContentObserver(mMainHandler) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, int userId) {
if (Objects.equals(userId,
mUserTracker.getUserId())) {
reload();
}
}
};
/**
* Observe user changes and react by potentially loading the custom clock for the new user.
*/
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
reload();
}
};
private final PluginManager mPluginManager;
@Nullable private final DockManager mDockManager;
/**
* Observe changes to dock state to know when to switch the clock face.
*/
private final DockEventListener mDockEventListener =
new DockEventListener() {
@Override
public void onEvent(int event) {
mIsDocked = (event == DockManager.STATE_DOCKED
|| event == DockManager.STATE_DOCKED_HIDE);
reload();
}
};
/**
* When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
* to show.
*/
private boolean mIsDocked;
/**
* Listeners for onClockChanged event.
*
* Each listener must receive a separate clock plugin instance. Otherwise, there could be
* problems like attempting to attach a view that already has a parent. To deal with this issue,
* each listener is associated with a collection of available clocks. When onClockChanged is
* fired the current clock plugin instance is retrieved from that listeners available clocks.
*/
private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
private final int mWidth;
private final int mHeight;
@Inject
public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
@Nullable DockManager dockManager, UserTracker userTracker,
@Main Executor mainExecutor) {
this(context, layoutInflater, pluginManager, colorExtractor,
context.getContentResolver(), userTracker, mainExecutor,
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
SettingsWrapper settingsWrapper, DockManager dockManager) {
mContext = context;
mPluginManager = pluginManager;
mContentResolver = contentResolver;
mSettingsWrapper = settingsWrapper;
mUserTracker = userTracker;
mMainExecutor = mainExecutor;
mDockManager = dockManager;
mPreviewClocks = new AvailableClocks();
Resources res = context.getResources();
addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
// Store the size of the display for generation of clock preview.
DisplayMetrics dm = res.getDisplayMetrics();
mWidth = dm.widthPixels;
mHeight = dm.heightPixels;
}
/**
* Add listener to be notified when clock implementation should change.
*/
public void addOnClockChangedListener(ClockChangedListener listener) {
if (mListeners.isEmpty()) {
register();
}
AvailableClocks availableClocks = new AvailableClocks();
for (int i = 0; i < mBuiltinClocks.size(); i++) {
availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
}
mListeners.put(listener, availableClocks);
mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
reload();
}
/**
* Remove listener added with {@link addOnClockChangedListener}.
*/
public void removeOnClockChangedListener(ClockChangedListener listener) {
AvailableClocks availableClocks = mListeners.remove(listener);
mPluginManager.removePluginListener(availableClocks);
if (mListeners.isEmpty()) {
unregister();
}
}
/**
* Get information about available clock faces.
*/
List<ClockInfo> getClockInfos() {
return mPreviewClocks.getInfo();
}
/**
* Get the current clock.
* @return current custom clock or null for default.
*/
@Nullable
ClockPlugin getCurrentClock() {
return mPreviewClocks.getCurrentClock();
}
@VisibleForTesting
boolean isDocked() {
return mIsDocked;
}
@VisibleForTesting
ContentObserver getContentObserver() {
return mContentObserver;
}
@VisibleForTesting
void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
ClockPlugin plugin = pluginSupplier.get();
mPreviewClocks.addClockPlugin(plugin);
mBuiltinClocks.add(pluginSupplier);
}
private void register() {
mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false, mContentObserver, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
false, mContentObserver, UserHandle.USER_ALL);
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
}
}
private void unregister() {
mPluginManager.removePluginListener(mPreviewClocks);
mContentResolver.unregisterContentObserver(mContentObserver);
mUserTracker.removeCallback(mUserChangedCallback);
if (mDockManager != null) {
mDockManager.removeListener(mDockEventListener);
}
}
private void reload() {
mPreviewClocks.reloadCurrentClock();
mListeners.forEach((listener, clocks) -> {
clocks.reloadCurrentClock();
final ClockPlugin clock = clocks.getCurrentClock();
if (Looper.myLooper() == Looper.getMainLooper()) {
listener.onClockChanged(clock instanceof DefaultClockController ? null : clock);
} else {
mMainHandler.post(() -> listener.onClockChanged(
clock instanceof DefaultClockController ? null : clock));
}
});
}
/**
* Listener for events that should cause the custom clock face to change.
*/
public interface ClockChangedListener {
/**
* Called when custom clock should change.
*
* @param clock Custom clock face to use. A null value indicates the default clock face.
*/
void onClockChanged(ClockPlugin clock);
}
/**
* Collection of available clocks.
*/
private final class AvailableClocks implements PluginListener<ClockPlugin> {
/**
* Map from expected value stored in settings to plugin for custom clock face.
*/
private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
/**
* Metadata about available clocks, such as name and preview images.
*/
private final List<ClockInfo> mClockInfo = new ArrayList<>();
/**
* Active ClockPlugin.
*/
@Nullable private ClockPlugin mCurrentClock;
@Override
public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
addClockPlugin(plugin);
reloadIfNeeded(plugin);
}
@Override
public void onPluginDisconnected(ClockPlugin plugin) {
removeClockPlugin(plugin);
reloadIfNeeded(plugin);
}
/**
* Get the current clock.
* @return current custom clock or null for default.
*/
@Nullable
ClockPlugin getCurrentClock() {
return mCurrentClock;
}
/**
* Get information about available clock faces.
*/
List<ClockInfo> getInfo() {
return mClockInfo;
}
/**
* Adds a clock plugin to the collection of available clocks.
*
* @param plugin The plugin to add.
*/
void addClockPlugin(ClockPlugin plugin) {
final String id = plugin.getClass().getName();
mClocks.put(plugin.getClass().getName(), plugin);
mClockInfo.add(ClockInfo.builder()
.setName(plugin.getName())
.setTitle(plugin::getTitle)
.setId(id)
.setThumbnail(plugin::getThumbnail)
.setPreview(() -> plugin.getPreview(mWidth, mHeight))
.build());
}
private void removeClockPlugin(ClockPlugin plugin) {
final String id = plugin.getClass().getName();
mClocks.remove(id);
for (int i = 0; i < mClockInfo.size(); i++) {
if (id.equals(mClockInfo.get(i).getId())) {
mClockInfo.remove(i);
break;
}
}
}
private void reloadIfNeeded(ClockPlugin plugin) {
final boolean wasCurrentClock = plugin == mCurrentClock;
reloadCurrentClock();
final boolean isCurrentClock = plugin == mCurrentClock;
if (wasCurrentClock || isCurrentClock) {
ClockManager.this.reload();
}
}
/**
* Update the current clock.
*/
void reloadCurrentClock() {
mCurrentClock = getClockPlugin();
}
private ClockPlugin getClockPlugin() {
ClockPlugin plugin = null;
if (ClockManager.this.isDocked()) {
final String name = mSettingsWrapper.getDockedClockFace(
mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
if (plugin != null) {
return plugin;
}
}
}
final String name = mSettingsWrapper.getLockScreenCustomClockFace(
mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
}
return plugin;
}
}
}

View File

@@ -1,183 +0,0 @@
/*
* 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.keyguard.clock;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Provider;
/**
* Exposes custom clock face options and provides realistic preview images.
*
* APIs:
*
* /list_options: List the available clock faces, which has the following columns
* name: name of the clock face
* title: title of the clock face
* id: value used to set the clock face
* thumbnail: uri of the thumbnail image, should be /thumbnail/{name}
* preview: uri of the preview image, should be /preview/{name}
*
* /thumbnail/{id}: Opens a file stream for the thumbnail image for clock face {id}.
*
* /preview/{id}: Opens a file stream for the preview image for clock face {id}.
*/
public final class ClockOptionsProvider extends ContentProvider {
private static final String TAG = "ClockOptionsProvider";
private static final String KEY_LIST_OPTIONS = "/list_options";
private static final String KEY_PREVIEW = "preview";
private static final String KEY_THUMBNAIL = "thumbnail";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TITLE = "title";
private static final String COLUMN_ID = "id";
private static final String COLUMN_THUMBNAIL = "thumbnail";
private static final String COLUMN_PREVIEW = "preview";
private static final String MIME_TYPE_PNG = "image/png";
private static final String CONTENT_SCHEME = "content";
private static final String AUTHORITY = "com.android.keyguard.clock";
@Inject
public Provider<List<ClockInfo>> mClockInfosProvider;
@VisibleForTesting
ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
mClockInfosProvider = clockInfosProvider;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
List<String> segments = uri.getPathSegments();
if (segments.size() > 0 && (KEY_PREVIEW.equals(segments.get(0))
|| KEY_THUMBNAIL.equals(segments.get(0)))) {
return MIME_TYPE_PNG;
}
return "vnd.android.cursor.dir/clock_faces";
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if (!KEY_LIST_OPTIONS.equals(uri.getPath())) {
return null;
}
MatrixCursor cursor = new MatrixCursor(new String[] {
COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
ClockInfo clock = clocks.get(i);
cursor.newRow()
.add(COLUMN_NAME, clock.getName())
.add(COLUMN_TITLE, clock.getTitle())
.add(COLUMN_ID, clock.getId())
.add(COLUMN_THUMBNAIL, createThumbnailUri(clock))
.add(COLUMN_PREVIEW, createPreviewUri(clock));
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
if (segments.size() != 2 || !(KEY_PREVIEW.equals(segments.get(0))
|| KEY_THUMBNAIL.equals(segments.get(0)))) {
throw new FileNotFoundException("Invalid preview url");
}
String id = segments.get(1);
if (TextUtils.isEmpty(id)) {
throw new FileNotFoundException("Invalid preview url, missing id");
}
ClockInfo clock = null;
List<ClockInfo> clocks = mClockInfosProvider.get();
for (int i = 0; i < clocks.size(); i++) {
if (id.equals(clocks.get(i).getId())) {
clock = clocks.get(i);
break;
}
}
if (clock == null) {
throw new FileNotFoundException("Invalid preview url, id not found");
}
return openPipeHelper(uri, MIME_TYPE_PNG, null, KEY_PREVIEW.equals(segments.get(0))
? clock.getPreview() : clock.getThumbnail(), new MyWriter());
}
private Uri createThumbnailUri(ClockInfo clock) {
return new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(KEY_THUMBNAIL)
.appendPath(clock.getId())
.build();
}
private Uri createPreviewUri(ClockInfo clock) {
return new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(KEY_PREVIEW)
.appendPath(clock.getId())
.build();
}
private static class MyWriter implements ContentProvider.PipeDataWriter<Bitmap> {
@Override
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
Bundle opts, Bitmap bitmap) {
try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
} catch (Exception e) {
Log.w(TAG, "fail to write to pipe", e);
}
}
}
}

View File

@@ -1,73 +0,0 @@
/*
* 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.keyguard.clock
import android.graphics.Color
import android.util.MathUtils
private const val PRIMARY_INDEX = 5
private const val SECONDARY_DARK_INDEX = 8
private const val SECONDARY_LIGHT_INDEX = 2
/**
* A helper class to extract colors from a clock face.
*/
class ClockPalette {
private var darkAmount: Float = 0f
private var accentPrimary: Int = Color.WHITE
private var accentSecondaryLight: Int = Color.WHITE
private var accentSecondaryDark: Int = Color.BLACK
private val lightHSV: FloatArray = FloatArray(3)
private val darkHSV: FloatArray = FloatArray(3)
private val hsv: FloatArray = FloatArray(3)
/** Returns a color from the palette as an RGB packed int. */
fun getPrimaryColor(): Int {
return accentPrimary
}
/** Returns either a light or dark color from the palette as an RGB packed int. */
fun getSecondaryColor(): Int {
Color.colorToHSV(accentSecondaryLight, lightHSV)
Color.colorToHSV(accentSecondaryDark, darkHSV)
for (i in 0..2) {
hsv[i] = MathUtils.lerp(darkHSV[i], lightHSV[i], darkAmount)
}
return Color.HSVToColor(hsv)
}
/** See {@link ClockPlugin#setColorPalette}. */
fun setColorPalette(supportsDarkText: Boolean, colorPalette: IntArray?) {
if (colorPalette == null || colorPalette.isEmpty()) {
accentPrimary = Color.WHITE
accentSecondaryLight = Color.WHITE
accentSecondaryDark = if (supportsDarkText) Color.BLACK else Color.WHITE
return
}
val length = colorPalette.size
accentPrimary = colorPalette[Math.max(0, length - PRIMARY_INDEX)]
accentSecondaryLight = colorPalette[Math.max(0, length - SECONDARY_LIGHT_INDEX)]
accentSecondaryDark = colorPalette[Math.max(0,
length - if (supportsDarkText) SECONDARY_DARK_INDEX else SECONDARY_LIGHT_INDEX)]
}
/** See {@link ClockPlugin#setDarkAmount}. */
fun setDarkAmount(darkAmount: Float) {
this.darkAmount = darkAmount
}
}

View File

@@ -1,62 +0,0 @@
/*
* 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.keyguard.clock;
import android.view.View;
/**
* Controls transition to dark state by cross fading between views.
*/
final class CrossFadeDarkController {
private final View mFadeInView;
private final View mFadeOutView;
/**
* Creates a new controller that fades between views.
*
* @param fadeInView View to fade in when transitioning to AOD.
* @param fadeOutView View to fade out when transitioning to AOD.
*/
CrossFadeDarkController(View fadeInView, View fadeOutView) {
mFadeInView = fadeInView;
mFadeOutView = fadeOutView;
}
/**
* Sets the amount the system has transitioned to the dark state.
*
* @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
*/
void setDarkAmount(float darkAmount) {
mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
if (darkAmount == 0f) {
mFadeInView.setVisibility(View.GONE);
} else {
if (mFadeInView.getVisibility() == View.GONE) {
mFadeInView.setVisibility(View.VISIBLE);
}
}
mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
if (darkAmount == 1f) {
mFadeOutView.setVisibility(View.GONE);
} else {
if (mFadeOutView.getVisibility() == View.GONE) {
mFadeOutView.setVisibility(View.VISIBLE);
}
}
}
}

View File

@@ -1,178 +0,0 @@
/*
* 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.keyguard.clock;
import android.app.WallpaperManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint.Style;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ClockPlugin;
import java.util.TimeZone;
/**
* Plugin for the default clock face used only to provide a preview.
*/
public class DefaultClockController implements ClockPlugin {
/**
* Resources used to get title and thumbnail.
*/
private final Resources mResources;
/**
* LayoutInflater used to inflate custom clock views.
*/
private final LayoutInflater mLayoutInflater;
/**
* Extracts accent color from wallpaper.
*/
private final SysuiColorExtractor mColorExtractor;
/**
* Renders preview from clock view.
*/
private final ViewPreviewer mRenderer = new ViewPreviewer();
/**
* Root view of preview.
*/
private View mView;
/**
* Text clock in preview view hierarchy.
*/
private TextView mTextTime;
/**
* Date showing below time in preview view hierarchy.
*/
private TextView mTextDate;
/**
* Create a DefaultClockController instance.
*
* @param res Resources contains title and thumbnail.
* @param inflater Inflater used to inflate custom clock views.
* @param colorExtractor Extracts accent color from wallpaper.
*/
public DefaultClockController(Resources res, LayoutInflater inflater,
SysuiColorExtractor colorExtractor) {
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
}
private void createViews() {
mView = mLayoutInflater.inflate(R.layout.default_clock_preview, null);
mTextTime = mView.findViewById(R.id.time);
mTextDate = mView.findViewById(R.id.date);
}
@Override
public void onDestroyView() {
mView = null;
mTextTime = null;
mTextDate = null;
}
@Override
public String getName() {
return "default";
}
@Override
public String getTitle() {
return mResources.getString(R.string.clock_title_default);
}
@Override
public Bitmap getThumbnail() {
return BitmapFactory.decodeResource(mResources, R.drawable.default_thumbnail);
}
@Override
public Bitmap getPreview(int width, int height) {
// Use the big clock view for the preview
View view = getBigClockView();
// Initialize state of plugin before generating preview.
setDarkAmount(1f);
setTextColor(Color.WHITE);
ColorExtractor.GradientColors colors = mColorExtractor.getColors(
WallpaperManager.FLAG_LOCK);
setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
onTimeTick();
return mRenderer.createPreview(view, width, height);
}
@Override
public View getView() {
return null;
}
@Override
public View getBigClockView() {
if (mView == null) {
createViews();
}
return mView;
}
@Override
public int getPreferredY(int totalHeight) {
return totalHeight / 2;
}
@Override
public void setStyle(Style style) {}
@Override
public void setTextColor(int color) {
mTextTime.setTextColor(color);
mTextDate.setTextColor(color);
}
@Override
public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {}
@Override
public void onTimeTick() {
}
@Override
public void setDarkAmount(float darkAmount) {}
@Override
public void onTimeZoneChanged(TimeZone timeZone) {}
@Override
public boolean shouldShowStatusArea() {
return true;
}
}

View File

@@ -1,103 +0,0 @@
/*
* 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.keyguard.clock;
import android.content.Context;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Clock composed of two images that rotate with the time.
*
* The images are the clock hands. ImageClock expects two child ImageViews
* with ids hour_hand and minute_hand.
*/
public class ImageClock extends FrameLayout {
private ImageView mHourHand;
private ImageView mMinuteHand;
private final Calendar mTime = Calendar.getInstance(TimeZone.getDefault());
private String mDescFormat;
private TimeZone mTimeZone;
public ImageClock(Context context) {
this(context, null);
}
public ImageClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
}
/**
* Call when the time changes to update the rotation of the clock hands.
*/
public void onTimeChanged() {
mTime.setTimeInMillis(System.currentTimeMillis());
final float hourAngle = mTime.get(Calendar.HOUR) * 30f + mTime.get(Calendar.MINUTE) * 0.5f;
mHourHand.setRotation(hourAngle);
final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
mMinuteHand.setRotation(minuteAngle);
setContentDescription(DateFormat.format(mDescFormat, mTime));
invalidate();
}
/**
* Call when the time zone has changed to update clock hands.
*
* @param timeZone The updated time zone that will be used.
*/
public void onTimeZoneChanged(TimeZone timeZone) {
mTimeZone = timeZone;
mTime.setTimeZone(timeZone);
}
/**
* Sets the colors to use on the clock face.
* @param dark Darker color obtained from color palette.
* @param light Lighter color obtained from color palette.
*/
public void setClockColors(int dark, int light) {
mHourHand.setColorFilter(dark);
mMinuteHand.setColorFilter(light);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mHourHand = findViewById(R.id.hour_hand);
mMinuteHand = findViewById(R.id.minute_hand);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mTime.setTimeZone(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
onTimeChanged();
}
}

View File

@@ -1,137 +0,0 @@
/*
* 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.keyguard.clock;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Wrapper around Settings used for testing.
*/
public class SettingsWrapper {
private static final String TAG = "ClockFaceSettings";
private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE;
private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE;
private static final String CLOCK_FIELD = "clock";
private final ContentResolver mContentResolver;
private final Migration mMigration;
SettingsWrapper(ContentResolver contentResolver) {
this(contentResolver, new Migrator(contentResolver));
}
@VisibleForTesting
SettingsWrapper(ContentResolver contentResolver, Migration migration) {
mContentResolver = contentResolver;
mMigration = migration;
}
/**
* Gets the value stored in settings for the custom clock face.
*
* @param userId ID of the user.
*/
String getLockScreenCustomClockFace(int userId) {
return decode(
Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId),
userId);
}
/**
* Gets the value stored in settings for the clock face to use when docked.
*
* @param userId ID of the user.
*/
String getDockedClockFace(int userId) {
return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId);
}
/**
* Decodes the string stored in settings, which should be formatted as JSON.
* @param value String stored in settings. If value is not JSON, then the settings is
* overwritten with JSON containing the prior value.
* @return ID of the clock face to show on AOD and lock screen. If value is not JSON, the value
* is returned.
*/
@VisibleForTesting
String decode(@Nullable String value, int userId) {
if (value == null) {
return value;
}
JSONObject json;
try {
json = new JSONObject(value);
} catch (JSONException ex) {
Log.e(TAG, "Settings value is not valid JSON", ex);
// The settings value isn't JSON since it didn't parse so migrate the value to JSON.
// TODO(b/135674383): Remove this migration path in the following release.
mMigration.migrate(value, userId);
return value;
}
try {
return json.getString(CLOCK_FIELD);
} catch (JSONException ex) {
Log.e(TAG, "JSON object does not contain clock field.", ex);
return null;
}
}
interface Migration {
void migrate(String value, int userId);
}
/**
* Implementation of {@link Migration} that writes valid JSON back to Settings.
*/
private static final class Migrator implements Migration {
private final ContentResolver mContentResolver;
Migrator(ContentResolver contentResolver) {
mContentResolver = contentResolver;
}
/**
* Migrate settings values that don't parse by converting to JSON format.
*
* Values in settings must be JSON to be backed up and restored. To help users maintain
* their current settings, convert existing values into the JSON format.
*
* TODO(b/135674383): Remove this migration code in the following release.
*/
@Override
public void migrate(String value, int userId) {
try {
JSONObject json = new JSONObject();
json.put(CLOCK_FIELD, value);
Settings.Secure.putStringForUser(mContentResolver, CUSTOM_CLOCK_FACE,
json.toString(),
userId);
} catch (JSONException ex) {
Log.e(TAG, "Failed migrating settings value to JSON format", ex);
}
}
}
}

View File

@@ -1,78 +0,0 @@
/*
* 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.keyguard.clock;
import android.content.Context;
import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
/**
* Computes preferred position of clock by considering height of status bar and lock icon.
*/
class SmallClockPosition {
/**
* Dimensions used to determine preferred clock position.
*/
private final int mStatusBarHeight;
private final int mKeyguardLockPadding;
private final int mKeyguardLockHeight;
private final int mBurnInOffsetY;
/**
* Amount of transition between AOD and lock screen.
*/
private float mDarkAmount;
SmallClockPosition(Context context) {
this(SystemBarUtils.getStatusBarHeight(context),
context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_padding),
context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_height),
context.getResources().getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
);
}
@VisibleForTesting
SmallClockPosition(int statusBarHeight, int lockPadding, int lockHeight, int burnInY) {
mStatusBarHeight = statusBarHeight;
mKeyguardLockPadding = lockPadding;
mKeyguardLockHeight = lockHeight;
mBurnInOffsetY = burnInY;
}
/**
* See {@link ClockPlugin#setDarkAmount}.
*/
void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
}
/**
* Gets the preferred Y position accounting for status bar and lock icon heights.
*/
int getPreferredY() {
// On AOD, clock needs to appear below the status bar with enough room for pixel shifting
int aodY = mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding
+ mBurnInOffsetY;
// On lock screen, clock needs to appear below the lock icon
int lockY = mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding;
return (int) MathUtils.lerp(lockY, aodY, mDarkAmount);
}
}

View File

@@ -1,105 +0,0 @@
/*
* 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.keyguard.clock;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Creates a preview image ({@link Bitmap}) of a {@link View} for a custom clock face.
*/
final class ViewPreviewer {
private static final String TAG = "ViewPreviewer";
/**
* Handler used to run {@link View#draw(Canvas)} on the main thread.
*/
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
/**
* Generate a realistic preview of a clock face.
*
* @param view view is used to generate preview image.
* @param width width of the preview image, should be the same as device width in pixels.
* @param height height of the preview image, should be the same as device height in pixels.
* @return bitmap of view.
*/
@Nullable
Bitmap createPreview(View view, int width, int height) {
if (view == null) {
return null;
}
FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() {
@Override
public Bitmap call() {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Draw clock view hierarchy to canvas.
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.BLACK);
dispatchVisibilityAggregated(view, true);
view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
view.layout(0, 0, width, height);
view.draw(canvas);
return bitmap;
}
});
if (Looper.myLooper() == Looper.getMainLooper()) {
task.run();
} else {
mMainHandler.post(task);
}
try {
return task.get();
} catch (Exception e) {
Log.e(TAG, "Error completing task", e);
return null;
}
}
private void dispatchVisibilityAggregated(View view, boolean isVisible) {
// Similar to View.dispatchVisibilityAggregated implementation.
final boolean thisVisible = view.getVisibility() == View.VISIBLE;
if (thisVisible || !isVisible) {
view.onVisibilityAggregated(isVisible);
}
if (view instanceof ViewGroup) {
isVisible = thisVisible && isVisible;
ViewGroup vg = (ViewGroup) view;
int count = vg.getChildCount();
for (int i = 0; i < count; i++) {
dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
}
}
}
}

View File

@@ -33,7 +33,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -323,7 +322,6 @@ public class Dependency {
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
@Inject @Main Lazy<Executor> mMainExecutor;
@Inject @Background Lazy<Executor> mBackgroundExecutor;
@Inject Lazy<ClockManager> mClockManager;
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
@@ -517,7 +515,6 @@ public class Dependency {
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,
mRemoteInputQuickSettingsDisabler::get);
mProviders.put(ClockManager.class, mClockManager::get);
mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);

View File

@@ -16,7 +16,6 @@
package com.android.systemui.dagger;
import com.android.keyguard.clock.ClockOptionsProvider;
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dependency;
@@ -249,11 +248,6 @@ public interface SysUIComponent {
*/
void inject(KeyguardSliceProvider keyguardSliceProvider);
/**
* Member injection into the supplied argument.
*/
void inject(ClockOptionsProvider clockOptionsProvider);
/**
* Member injection into the supplied argument.
*/

View File

@@ -23,7 +23,6 @@ import android.service.dreams.IDreamManager;
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.clock.ClockInfoModule;
import com.android.keyguard.dagger.ClockRegistryModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
@@ -160,7 +159,6 @@ import javax.inject.Named;
BiometricsModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockInfoModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
ConnectivityModule.class,

View File

@@ -1,76 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
import android.graphics.Color;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public final class AnalogClockControllerTest extends SysuiTestCase {
private AnalogClockController mClockController;
@Mock SysuiColorExtractor mMockColorExtractor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Resources res = getContext().getResources();
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mClockController = new AnalogClockController(res, layoutInflater,
mMockColorExtractor);
}
@Test
public void setDarkAmount_AOD() {
ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
View smallClock = smallClockFrame.getChildAt(0);
// WHEN dark amount is set to AOD
mClockController.setDarkAmount(1f);
// THEN small clock should be shown.
assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setColorPalette_setDigitalClock() {
ViewGroup smallClock = (ViewGroup) mClockController.getView();
// WHEN color palette is set
mClockController.setColorPalette(true, new int[]{Color.RED});
// THEN child of small clock should have text color set.
TextView digitalClock = (TextView) smallClock.getChildAt(0);
assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
}
}

View File

@@ -1,75 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
import android.graphics.Color;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public final class BubbleClockControllerTest extends SysuiTestCase {
private BubbleClockController mClockController;
@Mock SysuiColorExtractor mMockColorExtractor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Resources res = getContext().getResources();
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mClockController = new BubbleClockController(res, layoutInflater, mMockColorExtractor);
}
@Test
public void setDarkAmount_AOD() {
ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
View smallClock = smallClockFrame.getChildAt(0);
// WHEN dark amount is set to AOD
mClockController.setDarkAmount(1f);
// THEN small clock should not be shown.
assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setColorPalette_setDigitalClock() {
ViewGroup smallClock = (ViewGroup) mClockController.getView();
// WHEN text color is set
mClockController.setColorPalette(true, new int[]{Color.RED});
// THEN child of small clock should have text color set.
TextView digitalClock = (TextView) smallClock.getChildAt(0);
assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
}
}

View File

@@ -1,84 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import android.graphics.Bitmap;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public final class ClockInfoTest extends SysuiTestCase {
@Mock
private Supplier<Bitmap> mMockSupplier;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetName() {
final String name = "name";
ClockInfo info = ClockInfo.builder().setName(name).build();
assertThat(info.getName()).isEqualTo(name);
}
@Test
public void testGetTitle() {
final String title = "title";
ClockInfo info = ClockInfo.builder().setTitle(() -> title).build();
assertThat(info.getTitle()).isEqualTo(title);
}
@Test
public void testGetId() {
final String id = "id";
ClockInfo info = ClockInfo.builder().setId(id).build();
assertThat(info.getId()).isEqualTo(id);
}
@Test
public void testGetThumbnail() {
ClockInfo info = ClockInfo.builder().setThumbnail(mMockSupplier).build();
info.getThumbnail();
verify(mMockSupplier).get();
}
@Test
public void testGetPreview() {
ClockInfo info = ClockInfo.builder().setPreview(mMockSupplier).build();
info.getPreview();
verify(mMockSupplier).get();
}
}

View File

@@ -1,258 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
@RunWithLooper(setAsMainLooper = true)
public final class ClockManagerTest extends SysuiTestCase {
private static final String BUBBLE_CLOCK = BubbleClockController.class.getName();
private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class;
private static final int MAIN_USER_ID = 0;
private static final int SECONDARY_USER_ID = 11;
private static final Uri SETTINGS_URI = null;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private ClockManager mClockManager;
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
@Mock UserTracker mUserTracker;
@Mock SettingsWrapper mMockSettingsWrapper;
@Mock ClockManager.ClockChangedListener mMockListener1;
@Mock ClockManager.ClockChangedListener mMockListener2;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
LayoutInflater inflater = LayoutInflater.from(getContext());
mFakeDockManager = new DockManagerFake();
when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
mClockManager.addBuiltinClock(() -> new BubbleClockController(
getContext().getResources(), inflater, mMockColorExtractor));
mClockManager.addOnClockChangedListener(mMockListener1);
mClockManager.addOnClockChangedListener(mMockListener2);
verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
reset(mMockListener1, mMockListener2);
mContentObserver = mClockManager.getContentObserver();
}
@After
public void tearDown() {
mClockManager.removeOnClockChangedListener(mMockListener1);
mClockManager.removeOnClockChangedListener(mMockListener2);
}
@Test
public void dockEvent() {
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
assertThat(mClockManager.isDocked()).isTrue();
}
@Test
public void undockEvent() {
mFakeDockManager.setDockEvent(DockManager.STATE_NONE);
assertThat(mClockManager.isDocked()).isFalse();
}
@Test
public void getCurrentClock_default() {
// GIVEN that settings doesn't contain any values
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(null);
when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(null);
// WHEN settings change event is fired
mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
// THEN the result is null, indicated the default clock face should be used.
assertThat(mClockManager.getCurrentClock()).isNull();
}
@Test
public void getCurrentClock_customClock() {
// GIVEN that settings is set to the bubble clock face
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
// WHEN settings change event is fired
mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@Test
public void onClockChanged_customClock() {
// GIVEN that settings is set to the bubble clock face
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
// WHEN settings change event is fired
mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
// THEN the plugin is the bubble clock face.
ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class);
verify(mMockListener1).onClockChanged(captor.capture());
assertThat(captor.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@Test
public void onClockChanged_uniqueInstances() {
// GIVEN that settings is set to the bubble clock face
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
// WHEN settings change event is fired
mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
// THEN the listeners receive separate instances of the Bubble clock plugin.
ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class);
ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class);
verify(mMockListener1).onClockChanged(captor1.capture());
verify(mMockListener2).onClockChanged(captor2.capture());
assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
assertThat(captor1.getValue()).isNotSameInstanceAs(captor2.getValue());
}
@Test
public void getCurrentClock_badSettingsValue() {
// GIVEN that settings contains a value that doesn't correspond to a
// custom clock face.
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn("bad value");
// WHEN settings change event is fired
mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
// THEN the result is null.
assertThat(mClockManager.getCurrentClock()).isNull();
}
@Test
public void getCurrentClock_dockedDefault() {
// WHEN dock event is fired
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
// THEN the result is null, indicating the default clock face.
assertThat(mClockManager.getCurrentClock()).isNull();
}
@Test
public void getCurrentClock_dockedCustomClock() {
// GIVEN settings is set to the bubble clock face
when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
// WHEN dock event fires
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@Test
public void getCurrentClock_badDockedSettingsValue() {
// GIVEN settings contains a value that doesn't correspond to an available clock face.
when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
// WHEN dock event fires
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
// THEN the result is null.
assertThat(mClockManager.getCurrentClock()).isNull();
}
@Test
public void getCurrentClock_badDockedSettingsFallback() {
// GIVEN settings contains a value that doesn't correspond to an available clock face, but
// locked screen settings is set to bubble clock.
when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
// WHEN dock event is fired
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@Test
public void onUserChanged_defaultClock() {
// WHEN the user changes
switchUser(SECONDARY_USER_ID);
// THEN the plugin is null for the default clock face
assertThat(mClockManager.getCurrentClock()).isNull();
}
@Test
public void onUserChanged_customClock() {
// GIVEN that a second user has selected the bubble clock face
when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
BUBBLE_CLOCK);
// WHEN the user changes
switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@Test
public void onUserChanged_docked() {
// GIVEN device is docked
mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
// AND the second user as selected the bubble clock for the dock
when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
// WHEN the user changes
switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
private void switchUser(int newUser) {
when(mUserTracker.getUserId()).thenReturn(newUser);
mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
}
}

View File

@@ -1,189 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public final class ClockOptionsProviderTest extends SysuiTestCase {
private static final String CONTENT_SCHEME = "content";
private static final String AUTHORITY = "com.android.keyguard.clock";
private static final String LIST_OPTIONS = "list_options";
private static final String PREVIEW = "preview";
private static final String THUMBNAIL = "thumbnail";
private static final String MIME_TYPE_LIST_OPTIONS = "vnd.android.cursor.dir/clock_faces";
private static final String MIME_TYPE_PNG = "image/png";
private static final String NAME_COLUMN = "name";
private static final String TITLE_COLUMN = "title";
private static final String ID_COLUMN = "id";
private static final String PREVIEW_COLUMN = "preview";
private static final String THUMBNAIL_COLUMN = "thumbnail";
private ClockOptionsProvider mProvider;
private Supplier<List<ClockInfo>> mMockSupplier;
private List<ClockInfo> mClocks;
private Uri mListOptionsUri;
@Mock
private Supplier<Bitmap> mMockBitmapSupplier;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mClocks = new ArrayList<>();
mProvider = new ClockOptionsProvider(() -> mClocks);
mListOptionsUri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(LIST_OPTIONS)
.build();
}
@Test
public void testGetType_listOptions() {
Uri uri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(LIST_OPTIONS)
.build();
assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_LIST_OPTIONS);
}
@Test
public void testGetType_preview() {
Uri uri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(PREVIEW)
.appendPath("id")
.build();
assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
}
@Test
public void testGetType_thumbnail() {
Uri uri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(THUMBNAIL)
.appendPath("id")
.build();
assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
}
@Test
public void testQuery_noClocks() {
Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
assertThat(cursor.getCount()).isEqualTo(0);
}
@Test
public void testQuery_listOptions() {
mClocks.add(ClockInfo.builder()
.setName("name_a")
.setTitle(() -> "title_a")
.setId("id_a")
.build());
mClocks.add(ClockInfo.builder()
.setName("name_b")
.setTitle(() -> "title_b")
.setId("id_b")
.build());
Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
assertThat(cursor.getCount()).isEqualTo(2);
cursor.moveToFirst();
assertThat(cursor.getString(
cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_a");
assertThat(cursor.getString(
cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_a");
assertThat(cursor.getString(
cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_a");
assertThat(cursor.getString(
cursor.getColumnIndex(PREVIEW_COLUMN)))
.isEqualTo("content://com.android.keyguard.clock/preview/id_a");
assertThat(cursor.getString(
cursor.getColumnIndex(THUMBNAIL_COLUMN)))
.isEqualTo("content://com.android.keyguard.clock/thumbnail/id_a");
cursor.moveToNext();
assertThat(cursor.getString(
cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_b");
assertThat(cursor.getString(
cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_b");
assertThat(cursor.getString(
cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_b");
assertThat(cursor.getString(
cursor.getColumnIndex(PREVIEW_COLUMN)))
.isEqualTo("content://com.android.keyguard.clock/preview/id_b");
assertThat(cursor.getString(
cursor.getColumnIndex(THUMBNAIL_COLUMN)))
.isEqualTo("content://com.android.keyguard.clock/thumbnail/id_b");
}
@Test
public void testOpenFile_preview() throws Exception {
mClocks.add(ClockInfo.builder()
.setId("id")
.setPreview(mMockBitmapSupplier)
.build());
Uri uri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(PREVIEW)
.appendPath("id")
.build();
mProvider.openFile(uri, "r").close();
verify(mMockBitmapSupplier).get();
}
@Test
public void testOpenFile_thumbnail() throws Exception {
mClocks.add(ClockInfo.builder()
.setId("id")
.setThumbnail(mMockBitmapSupplier)
.build());
Uri uri = new Uri.Builder()
.scheme(CONTENT_SCHEME)
.authority(AUTHORITY)
.appendPath(THUMBNAIL)
.appendPath("id")
.build();
mProvider.openFile(uri, "r").close();
verify(mMockBitmapSupplier).get();
}
}

View File

@@ -1,97 +0,0 @@
/*
* 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.keyguard.clock
import android.graphics.Color
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ClockPaletteTest : SysuiTestCase() {
private lateinit var clockPalette: ClockPalette
private lateinit var colors: IntArray
@Before
fun setUp() {
clockPalette = ClockPalette()
// colors used are reds from light to dark.
val hsv: FloatArray = FloatArray(3)
Color.colorToHSV(Color.RED, hsv)
colors = IntArray(10)
val step: Float = (0f - hsv[2]) / colors.size
for (i in 0 until colors.size) {
hsv[2] += step
colors[i] = Color.HSVToColor(hsv)
}
}
@Test
fun testDark() {
// GIVEN on AOD
clockPalette.setDarkAmount(1f)
// AND GIVEN that wallpaper doesn't support dark text
clockPalette.setColorPalette(false, colors)
// THEN the secondary color should be lighter than the primary color
assertThat(value(clockPalette.getPrimaryColor()))
.isGreaterThan(value(clockPalette.getSecondaryColor()))
}
@Test
fun testDarkText() {
// GIVEN on lock screen
clockPalette.setDarkAmount(0f)
// AND GIVEN that wallpaper supports dark text
clockPalette.setColorPalette(true, colors)
// THEN the secondary color should be darker the primary color
assertThat(value(clockPalette.getPrimaryColor()))
.isLessThan(value(clockPalette.getSecondaryColor()))
}
@Test
fun testLightText() {
// GIVEN on lock screen
clockPalette.setDarkAmount(0f)
// AND GIVEN that wallpaper doesn't support dark text
clockPalette.setColorPalette(false, colors)
// THEN the secondary color should be darker than the primary color
assertThat(value(clockPalette.getPrimaryColor()))
.isGreaterThan(value(clockPalette.getSecondaryColor()))
}
@Test
fun testNullColors() {
// GIVEN on AOD
clockPalette.setDarkAmount(1f)
// AND GIVEN that wallpaper colors are null
clockPalette.setColorPalette(false, null)
// THEN the primary color should be whilte
assertThat(clockPalette.getPrimaryColor()).isEqualTo(Color.WHITE)
}
private fun value(color: Int): Float {
val hsv: FloatArray = FloatArray(3)
Color.colorToHSV(color, hsv)
return hsv[2]
}
}

View File

@@ -1,94 +0,0 @@
/*
* 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.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.widget.TextView;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public final class CrossFadeDarkControllerTest extends SysuiTestCase {
private View mViewFadeIn;
private View mViewFadeOut;
private CrossFadeDarkController mDarkController;
@Before
public void setUp() {
mViewFadeIn = new TextView(getContext());
mViewFadeIn.setVisibility(View.VISIBLE);
mViewFadeIn.setAlpha(1f);
mViewFadeOut = new TextView(getContext());
mViewFadeOut.setVisibility(View.VISIBLE);
mViewFadeOut.setAlpha(1f);
mDarkController = new CrossFadeDarkController(mViewFadeIn, mViewFadeOut);
}
@Test
public void setDarkAmount_fadeIn() {
// WHEN dark amount corresponds to AOD
mDarkController.setDarkAmount(1f);
// THEN fade in view should be faded in and fade out view faded out.
assertThat(mViewFadeIn.getAlpha()).isEqualTo(1f);
assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewFadeOut.getAlpha()).isEqualTo(0f);
assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void setDarkAmount_fadeOut() {
// WHEN dark amount corresponds to lock screen
mDarkController.setDarkAmount(0f);
// THEN fade out view should bed faded out and fade in view faded in.
assertThat(mViewFadeIn.getAlpha()).isEqualTo(0f);
assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewFadeOut.getAlpha()).isEqualTo(1f);
assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setDarkAmount_partialFadeIn() {
// WHEN dark amount corresponds to a partial transition
mDarkController.setDarkAmount(0.9f);
// THEN views should have intermediate alpha value.
assertThat(mViewFadeIn.getAlpha()).isGreaterThan(0f);
assertThat(mViewFadeIn.getAlpha()).isLessThan(1f);
assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setDarkAmount_partialFadeOut() {
// WHEN dark amount corresponds to a partial transition
mDarkController.setDarkAmount(0.1f);
// THEN views should have intermediate alpha value.
assertThat(mViewFadeOut.getAlpha()).isGreaterThan(0f);
assertThat(mViewFadeOut.getAlpha()).isLessThan(1f);
assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
}
}

View File

@@ -1,93 +0,0 @@
/*
* 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.keyguard.clock
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.json.JSONObject
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
private const val PACKAGE = "com.android.keyguard.clock.Clock"
private const val CLOCK_FIELD = "clock"
private const val TIMESTAMP_FIELD = "_applied_timestamp"
private const val USER_ID = 0
@RunWith(AndroidTestingRunner::class)
@SmallTest
class SettingsWrapperTest : SysuiTestCase() {
private lateinit var wrapper: SettingsWrapper
private lateinit var migration: SettingsWrapper.Migration
@Before
fun setUp() {
migration = mock(SettingsWrapper.Migration::class.java)
wrapper = SettingsWrapper(getContext().contentResolver, migration)
}
@Test
fun testDecodeUnnecessary() {
// GIVEN a settings value that doesn't need to be decoded
val value = PACKAGE
// WHEN the value is decoded
val decoded = wrapper.decode(value, USER_ID)
// THEN the same value is returned, because decoding isn't necessary.
// TODO(b/135674383): Null should be returned when the migration code in removed.
assertThat(decoded).isEqualTo(value)
// AND the value is migrated to JSON format
verify(migration).migrate(value, USER_ID)
}
@Test
fun testDecodeJSON() {
// GIVEN a settings value that is encoded in JSON
val json: JSONObject = JSONObject()
json.put(CLOCK_FIELD, PACKAGE)
json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
val value = json.toString()
// WHEN the value is decoded
val decoded = wrapper.decode(value, USER_ID)
// THEN the clock field should have been extracted
assertThat(decoded).isEqualTo(PACKAGE)
}
@Test
fun testDecodeJSONWithoutClockField() {
// GIVEN a settings value that doesn't contain the CLOCK_FIELD
val json: JSONObject = JSONObject()
json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
val value = json.toString()
// WHEN the value is decoded
val decoded = wrapper.decode(value, USER_ID)
// THEN null is returned
assertThat(decoded).isNull()
// AND the value is not migrated to JSON format
verify(migration, never()).migrate(value, USER_ID)
}
@Test
fun testDecodeNullJSON() {
assertThat(wrapper.decode(null, USER_ID)).isNull()
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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.keyguard.clock
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class SmallClockPositionTest : SysuiTestCase() {
private val statusBarHeight = 100
private val lockPadding = 15
private val lockHeight = 35
private val burnInY = 20
private lateinit var position: SmallClockPosition
@Before
fun setUp() {
position = SmallClockPosition(statusBarHeight, lockPadding, lockHeight, burnInY)
}
@Test
fun loadResources() {
// Cover constructor taking Resources object.
position = SmallClockPosition(context)
position.setDarkAmount(1f)
assertThat(position.preferredY).isGreaterThan(0)
}
@Test
fun darkPosition() {
// GIVEN on AOD
position.setDarkAmount(1f)
// THEN Y is sum of statusBarHeight, lockPadding, lockHeight, lockPadding, burnInY
assertThat(position.preferredY).isEqualTo(185)
}
@Test
fun lockPosition() {
// GIVEN on lock screen
position.setDarkAmount(0f)
// THEN Y position is statusBarHeight + lockPadding + lockHeight + lockPadding
// (100 + 15 + 35 + 15 = 165)
assertThat(position.preferredY).isEqualTo(165)
}
}

View File

@@ -1,63 +0,0 @@
/*
* 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.keyguard.clock
import android.content.Context
import com.google.common.truth.Truth.assertThat
import android.graphics.Canvas
import android.graphics.Color
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ViewPreviewerTest : SysuiTestCase() {
private lateinit var previewer: ViewPreviewer
private lateinit var view: View
@Before
fun setUp() {
previewer = ViewPreviewer()
view = TestView(context)
}
@Test
fun testCreatePreview() {
val width = 100
val height = 100
// WHEN a preview image is created
val bitmap = previewer.createPreview(view, width, height)!!
// THEN the bitmap has the expected width and height
assertThat(bitmap.height).isEqualTo(height)
assertThat(bitmap.width).isEqualTo(width)
assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED)
}
class TestView(context: Context) : View(context) {
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawColor(Color.RED)
}
}
}