java.lang.Object
↳Drawable
↳androidx.wear.watchface.complications.rendering.ComplicationDrawable
Overview
A styleable drawable object that draws complications. You can create a ComplicationDrawable from
XML inflation or by using one of the constructor methods.
Constructing a ComplicationDrawable
To construct a ComplicationDrawable programmatically, use the ComplicationDrawable.ComplicationDrawable(Context) constructor. Afterwards, styling attributes you want to modify
can be set via set methods.
public void onCreate(SurfaceHolder holder) {
...
ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
complicationDrawable.setBackgroundColorActive(backgroundColor);
complicationDrawable.setTextColorActive(textColor);
...
}
Constructing a ComplicationDrawable from XML
Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
styling attributes at once without calling any set methods. You may also use different XML files
to switch between different styles your watch face supports.
To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
your project's res/drawable folder. A ComplicationDrawable with red text and white title
in active mode, and white text and white title in ambient mode would look like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.complications.rendering.ComplicationDrawable
xmlns:app="http://schemas.android.com/apk/res-auto"
app:textColor="#FFFF0000"
app:titleColor="#FFFFFFFF">
<ambient
app:textColor="#FFFFFFFF" />
</android.support.wearable.complications.rendering.ComplicationDrawable>
A top-level drawable tag with the class attribute may also be used to
construct a ComplicationDrawable from an XML file:
<?xml version="1.0" encoding="utf-8"?>
<drawable
class="android.support.wearable.complications.rendering.ComplicationDrawable"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:textColor="#FFFF0000"
app:titleColor="#FFFFFFFF">
<ambient
app:textColor="#FFFFFFFF" />
</drawable>
To inflate a ComplicationDrawable from XML file, use the ComplicationDrawable.getDrawable(Context, int)
method. ComplicationDrawable needs access to the current context in order to style and draw
the complication.
public void onCreate(SurfaceHolder holder) {
...
ComplicationDrawable complicationDrawable = (ComplicationDrawable)
getDrawable(R.drawable.complication);
complicationDrawable.setContext(WatchFaceService.this);
...
}
Syntax:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.complications.rendering.ComplicationDrawable
xmlns:app="http://schemas.android.com/apk/res-auto"
app:backgroundColor="color"
app:backgroundDrawable="drawable"
app:borderColor="color"
app:borderDashGap="dimension"
app:borderDashWidth="dimension"
app:borderRadius="dimension"
app:borderStyle="none|solid|dashed"
app:borderWidth="dimension"
app:highlightColor="color"
app:iconColor="color"
app:rangedValuePrimaryColor="color"
app:rangedValueProgressHidden="boolean"
app:rangedValueRingWidth="dimension"
app:rangedValueSecondaryColor="color"
app:textColor="color"
app:textSize="dimension"
app:textTypeface="string"
app:titleColor="color"
app:titleSize="dimension"
app:titleTypeface="string">
<ambient
app:backgroundColor="color"
app:backgroundDrawable="drawable"
app:borderColor="color"
app:borderDashGap="dimension"
app:borderDashWidth="dimension"
app:borderRadius="dimension"
app:borderStyle="none|solid|dashed"
app:borderWidth="dimension"
app:highlightColor="color"
app:iconColor="color"
app:rangedValuePrimaryColor="color"
app:rangedValueRingWidth="dimension"
app:rangedValueSecondaryColor="color"
app:textColor="color"
app:textSize="dimension"
app:textTypeface="string"
app:titleColor="color"
app:titleSize="dimension"
app:titleTypeface="string" />
</android.support.wearable.complications.rendering.ComplicationDrawable>
Attributes of the top-level tag apply to both active and ambient modes while attributes of the
inner ambient tag only apply to ambient mode. As an exception, top-level only rangedValueProgressHidden attribute applies to both modes, and cannot be overridden in ambient
mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
rangedValuePrimaryColor and rangedValueSecondaryColor to instead.
Drawing a ComplicationDrawable
Depending on the size and shape of the bounds, the layout of the complication may change. For
instance, a short text complication with an icon that is drawn on square bounds would draw the
icon above the short text, but a short text complication with an icon that is drawn on wide
rectangular bounds might draw the icon to the left of the short text instead.
Summary
Methods |
---|
public void | draw(Canvas canvas)
Draws the complication for the last known time. |
public ComplicationStyle | getActiveStyle()
Returns complication style for active mode. |
public ComplicationStyle | getAmbientStyle()
Returns complication style for ambient mode. |
public android.support.wearable.complications.ComplicationData | getComplicationData()
Returns the android.support.wearable.complications.ComplicationData to be drawn by this ComplicationDrawable. |
public androidx.wear.watchface.complications.rendering.ComplicationRenderer | getComplicationRenderer()
Returns complication renderer. |
public Context | getContext()
Returns the used to render the complication. |
public long | getCurrentTimeMillis()
Returns the time in milliseconds since the epoch used for rendering android.support.wearable.complications.ComplicationData
with time dependent text. |
public static ComplicationDrawable | getDrawable(Context context, int id)
Creates a ComplicationDrawable from a resource. |
public long | getHighlightDuration()
Returns the highlight duration. |
public java.lang.CharSequence | getNoDataText()
|
public int | getOpacity()
|
public void | inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
Inflates this ComplicationDrawable from an XML resource. |
public boolean | isBurnInProtectionOn()
Whether the complication, when rendering in ambient mode, should apply a style suitable for
display on devices with burn in protection. |
public boolean | isHighlighted()
Returns whether the complication is currently highlighted. |
public boolean | isInAmbientMode()
Returns whether the complication is rendered in ambient mode. |
public boolean | isLowBitAmbient()
Returns whether the complication, when rendering in ambient mode, should apply a style
suitable for low bit ambient mode. |
public boolean | isRangedValueProgressHidden()
Returns true if the ranged value progress is hidden, false otherwise. |
protected void | onBoundsChange(Rect bounds)
|
public boolean | onTap(int x, int y)
Sends the tap action for the complication if tap coordinates are inside the complication
bounds. |
public void | setAlpha(int alpha)
Does nothing. |
public void | setBurnInProtection(boolean burnInProtection)
Sets whether the complication, when rendering in ambient mode, should apply a style suitable
for display on devices with burn in protection. |
public void | setColorFilter(ColorFilter colorFilter)
Does nothing. |
public void | setComplicationData(android.support.wearable.complications.ComplicationData complicationData)
Sets the complication data to be drawn. |
public void | setContext(Context context)
Sets the context used to render the complication. |
public void | setCurrentTimeMillis(long currentTimeMillis)
Sets the current time in mulliseconds since the epoch. |
public void | setHighlightDuration(long highlightDurationMillis)
Sets the duration for the complication to stay highlighted after calling the ComplicationDrawable.onTap(int, int) method. |
public void | setHighlighted(boolean isHighlighted)
Sets whether the complication is currently highlighted. |
public void | setInAmbientMode(boolean inAmbientMode)
Sets whether the complication should be rendered in ambient mode. |
public void | setLowBitAmbient(boolean lowBitAmbient)
Sets whether the complication, when rendering in ambient mode, should apply a style suitable
for low bit ambient mode. |
public void | setNoDataText(java.lang.CharSequence noDataText)
Sets the text to be rendered when android.support.wearable.complications.ComplicationData is of type TYPE_NO_DATA . |
public void | setRangedValueProgressHidden(boolean rangedValueProgressHidden)
Sets if the ranged value progress should be hidden when android.support.wearable.complications.ComplicationData is of type
TYPE_RANGED_VALUE . |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
ComplicationDrawable()
Default constructor.
public
ComplicationDrawable(Context context)
Creates a ComplicationDrawable using the given context. If this constructor is used, calling
ComplicationDrawable.setContext(Context) may not be necessary.
Methods
Creates a ComplicationDrawable from a resource.
Parameters:
context: The to load the resource from
id: The id of the resource to load
Returns:
The ComplicationDrawable loaded from the specified resource id or null if it
doesn't exist.
public void
setContext(Context context)
Sets the context used to render the complication. If a context is not set,
ComplicationDrawable will throw an java.lang.IllegalStateException
if one of
ComplicationDrawable.draw(Canvas), ComplicationDrawable, or ComplicationDrawable.setComplicationData(ComplicationData) is called.
While this can be called from any context, ideally, a
androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
permission dialogs by the ComplicationDrawable.onTap(int, int) method, in case current watch face
doesn't have the permission to receive complication data.
If this ComplicationDrawable is retrieved using Resources
,
this method must be called before calling any of the methods mentioned above.
If this ComplicationDrawable is not inflated from an XML file, this method will reset the
style to match the default values, so if ComplicationDrawable.ComplicationDrawable() is used to construct
a ComplicationDrawable, this method should be called right after.
public Context
getContext()
Returns the used to render the complication.
public void
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
for each ComplicationDrawable. Note that framework may have called this once to create the
ComplicationDrawable instance from an XML resource.
Parameters:
r: Resources used to resolve attribute values
parser: XML parser from which to inflate this ComplicationDrawable
attrs: Base set of attribute values
theme: Ignored by ComplicationDrawable
public void
draw(Canvas canvas)
Draws the complication for the last known time. Last known time is derived from
ComplicationDrawable#setCurrentTimeMillis(long)}.
Parameters:
canvas: Canvas for the complication to be drawn onto
public void
setAlpha(int alpha)
Does nothing.
public void
setColorFilter(ColorFilter colorFilter)
Does nothing. Use ComplicationStyle.setImageColorFilter(ColorFilter) instead to apply
color filter to small and large images.
Deprecated: This method is no longer used in graphics optimizations
protected void
onBoundsChange(Rect bounds)
public void
setNoDataText(java.lang.CharSequence noDataText)
Sets the text to be rendered when android.support.wearable.complications.ComplicationData
is of type TYPE_NO_DATA
. If noDataText is null, an empty text will be
rendered.
public void
setRangedValueProgressHidden(boolean rangedValueProgressHidden)
Sets if the ranged value progress should be hidden when android.support.wearable.complications.ComplicationData
is of type
TYPE_RANGED_VALUE
.
Parameters:
rangedValueProgressHidden: true if progress should be hidden, false
otherwise
public boolean
isRangedValueProgressHidden()
Returns true if the ranged value progress is hidden, false otherwise.
public void
setComplicationData(android.support.wearable.complications.ComplicationData complicationData)
Sets the complication data to be drawn. If complicationData is null, nothing
will be drawn when ComplicationDrawable.draw(Canvas) is called.
public android.support.wearable.complications.ComplicationData
getComplicationData()
Returns the android.support.wearable.complications.ComplicationData
to be drawn by this ComplicationDrawable.
public void
setInAmbientMode(boolean inAmbientMode)
Sets whether the complication should be rendered in ambient mode.
public boolean
isInAmbientMode()
Returns whether the complication is rendered in ambient mode.
public void
setLowBitAmbient(boolean lowBitAmbient)
Sets whether the complication, when rendering in ambient mode, should apply a style suitable
for low bit ambient mode.
public boolean
isLowBitAmbient()
Returns whether the complication, when rendering in ambient mode, should apply a style
suitable for low bit ambient mode.
public void
setBurnInProtection(boolean burnInProtection)
Sets whether the complication, when rendering in ambient mode, should apply a style suitable
for display on devices with burn in protection.
public boolean
isBurnInProtectionOn()
Whether the complication, when rendering in ambient mode, should apply a style suitable for
display on devices with burn in protection.
public void
setCurrentTimeMillis(long currentTimeMillis)
Sets the current time in mulliseconds since the epoch. This will be used to render
android.support.wearable.complications.ComplicationData
with time dependent text.
Parameters:
currentTimeMillis: time in milliseconds since the epoch
public long
getCurrentTimeMillis()
Returns the time in milliseconds since the epoch used for rendering android.support.wearable.complications.ComplicationData
with time dependent text.
public void
setHighlighted(boolean isHighlighted)
Sets whether the complication is currently highlighted. This may be called by a watch face
when a complication is tapped.
If watch face is in ambient mode, highlight will not be visible even if this is set to
true, because it may cause burn-in or power inefficiency.
public boolean
isHighlighted()
Returns whether the complication is currently highlighted.
public boolean
onTap(int x, int y)
Sends the tap action for the complication if tap coordinates are inside the complication
bounds.
This method will also highlight the complication. The highlight duration is 300
milliseconds by default but can be modified using the ComplicationDrawable.setHighlightDuration(long)
method.
If android.support.wearable.complications.ComplicationData
has the type TYPE_NO_PERMISSION
, this
method will launch an intent to request complication permission for the watch face. This will
only work if the context set by ComplicationDrawable.getDrawable(Context, int) or the constructor is an
instance of WatchFaceService.
Parameters:
x: X coordinate of the tap relative to screen origin
y: Y coordinate of the tap relative to screen origin
Returns:
true if the action was successful, false if complication data is not
set, the complication has no tap action, the tap action (i.e. android.app.PendingIntent
) is cancelled, or the given x and y are not inside the
complication bounds.
public void
setHighlightDuration(long highlightDurationMillis)
Sets the duration for the complication to stay highlighted after calling the ComplicationDrawable.onTap(int, int) method. Default value is 300 milliseconds. Setting highlight duration to 0
disables highlighting.
Parameters:
highlightDurationMillis: highlight duration in milliseconds
public long
getHighlightDuration()
Returns the highlight duration.
Returns complication style for active mode.
Returns complication style for ambient mode.
public androidx.wear.watchface.complications.rendering.ComplicationRenderer
getComplicationRenderer()
Returns complication renderer.
public java.lang.CharSequence
getNoDataText()
Source
/*
* Copyright 2020 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 androidx.wear.watchface.complications.rendering;
import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.support.wearable.complications.ComplicationData;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting;
import androidx.wear.complications.ComplicationHelperActivity;
import androidx.wear.watchface.complications.rendering.ComplicationRenderer.OnInvalidateListener;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Objects;
/**
* A styleable drawable object that draws complications. You can create a ComplicationDrawable from
* XML inflation or by using one of the constructor methods.
*
* <h3>Constructing a ComplicationDrawable</h3>
*
* <p>To construct a ComplicationDrawable programmatically, use the {@link
* #ComplicationDrawable(Context)} constructor. Afterwards, styling attributes you want to modify
* can be set via set methods.
*
* <pre>
* public void onCreate(SurfaceHolder holder) {
* ...
* ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
* complicationDrawable.setBackgroundColorActive(backgroundColor);
* complicationDrawable.setTextColorActive(textColor);
* ...
* }</pre>
*
* <h3>Constructing a ComplicationDrawable from XML</h3>
*
* <p>Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
* styling attributes at once without calling any set methods. You may also use different XML files
* to switch between different styles your watch face supports.
*
* <p>To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
* your project's {@code res/drawable} folder. A ComplicationDrawable with red text and white title
* in active mode, and white text and white title in ambient mode would look like this:
*
* <pre>
* <?xml version="1.0" encoding="utf-8"?>
* <android.support.wearable.complications.rendering.ComplicationDrawable
* xmlns:app="http://schemas.android.com/apk/res-auto"
* app:textColor="#FFFF0000"
* app:titleColor="#FFFFFFFF">
* <ambient
* app:textColor="#FFFFFFFF" />
* </android.support.wearable.complications.rendering.ComplicationDrawable>
* </pre>
*
* <p>A top-level {@code drawable} tag with the {@code class} attribute may also be used to
* construct a ComplicationDrawable from an XML file:
*
* <pre>
* <?xml version="1.0" encoding="utf-8"?>
* <drawable
* class="android.support.wearable.complications.rendering.ComplicationDrawable"
* xmlns:app="http://schemas.android.com/apk/res-auto"
* app:textColor="#FFFF0000"
* app:titleColor="#FFFFFFFF">
* <ambient
* app:textColor="#FFFFFFFF" />
* </drawable></pre>
*
* <p>To inflate a ComplicationDrawable from XML file, use the {@link #getDrawable(Context, int)}
* method. ComplicationDrawable needs access to the current context in order to style and draw
* the complication.
*
* <pre>
* public void onCreate(SurfaceHolder holder) {
* ...
* ComplicationDrawable complicationDrawable = (ComplicationDrawable)
* getDrawable(R.drawable.complication);
* complicationDrawable.setContext(WatchFaceService.this);
* ...
* }</pre>
*
* <h4>Syntax:</h4>
*
* <pre>
* <?xml version="1.0" encoding="utf-8"?>
* <android.support.wearable.complications.rendering.ComplicationDrawable
* xmlns:app="http://schemas.android.com/apk/res-auto"
* app:backgroundColor="color"
* app:backgroundDrawable="drawable"
* app:borderColor="color"
* app:borderDashGap="dimension"
* app:borderDashWidth="dimension"
* app:borderRadius="dimension"
* app:borderStyle="none|solid|dashed"
* app:borderWidth="dimension"
* app:highlightColor="color"
* app:iconColor="color"
* app:rangedValuePrimaryColor="color"
* app:rangedValueProgressHidden="boolean"
* app:rangedValueRingWidth="dimension"
* app:rangedValueSecondaryColor="color"
* app:textColor="color"
* app:textSize="dimension"
* app:textTypeface="string"
* app:titleColor="color"
* app:titleSize="dimension"
* app:titleTypeface="string">
* <ambient
* app:backgroundColor="color"
* app:backgroundDrawable="drawable"
* app:borderColor="color"
* app:borderDashGap="dimension"
* app:borderDashWidth="dimension"
* app:borderRadius="dimension"
* app:borderStyle="none|solid|dashed"
* app:borderWidth="dimension"
* app:highlightColor="color"
* app:iconColor="color"
* app:rangedValuePrimaryColor="color"
* app:rangedValueRingWidth="dimension"
* app:rangedValueSecondaryColor="color"
* app:textColor="color"
* app:textSize="dimension"
* app:textTypeface="string"
* app:titleColor="color"
* app:titleSize="dimension"
* app:titleTypeface="string" />
* </android.support.wearable.complications.rendering.ComplicationDrawable>
* </pre>
*
* <p>Attributes of the top-level tag apply to both active and ambient modes while attributes of the
* inner {@code ambient} tag only apply to ambient mode. As an exception, top-level only {@code
* rangedValueProgressHidden} attribute applies to both modes, and cannot be overridden in ambient
* mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
* {@code rangedValuePrimaryColor} and {@code rangedValueSecondaryColor} to {@link
* android.graphics.Color#TRANSPARENT} instead.
*
* <h3>Drawing a ComplicationDrawable</h3>
*
* <p>Depending on the size and shape of the bounds, the layout of the complication may change. For
* instance, a short text complication with an icon that is drawn on square bounds would draw the
* icon above the short text, but a short text complication with an icon that is drawn on wide
* rectangular bounds might draw the icon to the left of the short text instead.
*/
public final class ComplicationDrawable extends Drawable {
private Context mContext;
private ComplicationRenderer mComplicationRenderer;
private final ComplicationStyle mActiveStyle;
private final ComplicationStyle mAmbientStyle;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final Runnable mUnhighlightRunnable =
() -> {
setHighlighted(false);
invalidateSelf();
};
private final OnInvalidateListener mRendererInvalidateListener = () -> invalidateSelf();
private CharSequence mNoDataText;
private long mHighlightDuration;
private long mCurrentTimeMillis;
private boolean mInAmbientMode;
private boolean mLowBitAmbient;
private boolean mBurnInProtection;
private boolean mHighlighted;
private boolean mRangedValueProgressHidden;
private boolean mIsInflatedFromXml;
private boolean mAlreadyStyled;
/** Default constructor. */
public ComplicationDrawable() {
mActiveStyle = new ComplicationStyle();
mAmbientStyle = new ComplicationStyle();
}
/**
* Creates a ComplicationDrawable using the given context. If this constructor is used, calling
* {@link #setContext(Context)} may not be necessary.
*/
public ComplicationDrawable(@NonNull Context context) {
this();
setContext(context);
}
public ComplicationDrawable(@NonNull ComplicationDrawable drawable) {
mActiveStyle = new ComplicationStyle(drawable.mActiveStyle);
mAmbientStyle = new ComplicationStyle(drawable.mAmbientStyle);
mNoDataText = drawable.mNoDataText.subSequence(0, drawable.mNoDataText.length());
mHighlightDuration = drawable.mHighlightDuration;
mCurrentTimeMillis = drawable.mCurrentTimeMillis;
setBounds(drawable.getBounds());
mInAmbientMode = drawable.mInAmbientMode;
mLowBitAmbient = drawable.mLowBitAmbient;
mBurnInProtection = drawable.mBurnInProtection;
mHighlighted = false;
mRangedValueProgressHidden = drawable.mRangedValueProgressHidden;
mIsInflatedFromXml = drawable.mIsInflatedFromXml;
mAlreadyStyled = true;
}
/**
* Creates a ComplicationDrawable from a resource.
*
* @param context The {@link Context} to load the resource from
* @param id The id of the resource to load
* @return The {@link ComplicationDrawable} loaded from the specified resource id or null if it
* doesn't exist.
*/
@Nullable
public static ComplicationDrawable getDrawable(@NonNull Context context, int id) {
if (context == null) {
throw new IllegalArgumentException("Argument \"context\" should not be null.");
}
ComplicationDrawable drawable = (ComplicationDrawable) context.getDrawable(id);
if (drawable == null) {
return null;
}
drawable.setContext(context);
return drawable;
}
/** Sets the style to default values using resources. */
private static void setStyleToDefaultValues(ComplicationStyle style, Resources r) {
style.setBackgroundColor(
r.getColor(R.color.complicationDrawable_backgroundColor, null));
style.setTextColor(r.getColor(R.color.complicationDrawable_textColor, null));
style.setTitleColor(r.getColor(R.color.complicationDrawable_titleColor, null));
style.setTextTypeface(
Typeface.create(
r.getString(R.string.complicationDrawable_textTypeface), Typeface.NORMAL));
style.setTitleTypeface(
Typeface.create(
r.getString(R.string.complicationDrawable_titleTypeface), Typeface.NORMAL));
style.setTextSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize));
style.setTitleSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize));
style.setIconColor(r.getColor(R.color.complicationDrawable_iconColor, null));
style.setBorderColor(r.getColor(R.color.complicationDrawable_borderColor, null));
style.setBorderWidth(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth));
style.setBorderRadius(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius));
style.setBorderStyle(r.getInteger(R.integer.complicationDrawable_borderStyle));
style.setBorderDashWidth(
r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth));
style.setBorderDashGap(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap));
style.setRangedValueRingWidth(
r.getDimensionPixelSize(R.dimen.complicationDrawable_rangedValueRingWidth));
style.setRangedValuePrimaryColor(
r.getColor(R.color.complicationDrawable_rangedValuePrimaryColor, null));
style.setRangedValueSecondaryColor(
r.getColor(R.color.complicationDrawable_rangedValueSecondaryColor, null));
style.setHighlightColor(r.getColor(R.color.complicationDrawable_highlightColor, null));
}
/**
* Sets the context used to render the complication. If a context is not set,
* ComplicationDrawable will throw an {@link IllegalStateException} if one of
* {@link #draw(Canvas)}, {@link #setBounds(Rect)}, or {@link
* #setComplicationData(ComplicationData)} is called.
*
* <p>While this can be called from any context, ideally, a
* androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
* permission dialogs by the {@link #onTap(int, int)} method, in case current watch face
* doesn't have the permission to receive complication data.
*
* <p>If this ComplicationDrawable is retrieved using {@link Resources#getDrawable(int, Theme)},
* this method must be called before calling any of the methods mentioned above.
*
* <p>If this ComplicationDrawable is not inflated from an XML file, this method will reset the
* style to match the default values, so if {@link #ComplicationDrawable()} is used to construct
* a ComplicationDrawable, this method should be called right after.
*/
public void setContext(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("Argument \"context\" should not be null.");
}
if (Objects.equals(context, mContext)) {
return;
}
mContext = context;
if (!mIsInflatedFromXml && !mAlreadyStyled) {
setStyleToDefaultValues(mActiveStyle, context.getResources());
setStyleToDefaultValues(mAmbientStyle, context.getResources());
}
if (!mAlreadyStyled) {
mHighlightDuration = context.getResources()
.getInteger(R.integer.complicationDrawable_highlightDurationMs);
}
mComplicationRenderer = new ComplicationRenderer(mContext, mActiveStyle, mAmbientStyle);
mComplicationRenderer.setOnInvalidateListener(mRendererInvalidateListener);
if (mNoDataText == null) {
setNoDataText(context.getString(R.string.complicationDrawable_noDataText));
} else {
mComplicationRenderer.setNoDataText(mNoDataText);
}
mComplicationRenderer.setRangedValueProgressHidden(mRangedValueProgressHidden);
mComplicationRenderer.setBounds(getBounds());
}
/**
* Returns the {@link Context} used to render the complication.
*/
@Nullable public Context getContext() {
return mContext;
}
private void inflateAttributes(Resources r, XmlPullParser parser) {
TypedArray a =
r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
setRangedValueProgressHidden(
a.getBoolean(R.styleable.ComplicationDrawable_rangedValueProgressHidden, false));
a.recycle();
}
private void inflateStyle(boolean isAmbient, Resources r, XmlPullParser parser) {
TypedArray a =
r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
ComplicationStyle complicationStyle = isAmbient ? mAmbientStyle : mActiveStyle;
if (a.hasValue(R.styleable.ComplicationDrawable_backgroundColor)) {
complicationStyle.setBackgroundColor(
a.getColor(
R.styleable.ComplicationDrawable_backgroundColor,
r.getColor(R.color.complicationDrawable_backgroundColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_backgroundDrawable)) {
complicationStyle.setBackgroundDrawable(
a.getDrawable(R.styleable.ComplicationDrawable_backgroundDrawable));
}
if (a.hasValue(R.styleable.ComplicationDrawable_textColor)) {
complicationStyle.setTextColor(
a.getColor(
R.styleable.ComplicationDrawable_textColor,
r.getColor(R.color.complicationDrawable_textColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_titleColor)) {
complicationStyle.setTitleColor(
a.getColor(
R.styleable.ComplicationDrawable_titleColor,
r.getColor(R.color.complicationDrawable_titleColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_textTypeface)) {
complicationStyle.setTextTypeface(
Typeface.create(
a.getString(R.styleable.ComplicationDrawable_textTypeface),
Typeface.NORMAL));
}
if (a.hasValue(R.styleable.ComplicationDrawable_titleTypeface)) {
complicationStyle.setTitleTypeface(
Typeface.create(
a.getString(R.styleable.ComplicationDrawable_titleTypeface),
Typeface.NORMAL));
}
if (a.hasValue(R.styleable.ComplicationDrawable_textSize)) {
complicationStyle.setTextSize(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_textSize,
r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_titleSize)) {
complicationStyle.setTitleSize(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_titleSize,
r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_iconColor)) {
complicationStyle.setIconColor(
a.getColor(
R.styleable.ComplicationDrawable_iconColor,
r.getColor(R.color.complicationDrawable_iconColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderColor)) {
complicationStyle.setBorderColor(
a.getColor(
R.styleable.ComplicationDrawable_borderColor,
r.getColor(R.color.complicationDrawable_borderColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderRadius)) {
complicationStyle.setBorderRadius(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_borderRadius,
r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderStyle)) {
complicationStyle.setBorderStyle(
a.getInt(
R.styleable.ComplicationDrawable_borderStyle,
r.getInteger(R.integer.complicationDrawable_borderStyle)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderDashWidth)) {
complicationStyle.setBorderDashWidth(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_borderDashWidth,
r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderDashGap)) {
complicationStyle.setBorderDashGap(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_borderDashGap,
r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_borderWidth)) {
complicationStyle.setBorderWidth(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_borderWidth,
r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueRingWidth)) {
complicationStyle.setRangedValueRingWidth(
a.getDimensionPixelSize(
R.styleable.ComplicationDrawable_rangedValueRingWidth,
r.getDimensionPixelSize(
R.dimen.complicationDrawable_rangedValueRingWidth)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_rangedValuePrimaryColor)) {
complicationStyle.setRangedValuePrimaryColor(
a.getColor(
R.styleable.ComplicationDrawable_rangedValuePrimaryColor,
r.getColor(
R.color.complicationDrawable_rangedValuePrimaryColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueSecondaryColor)) {
complicationStyle.setRangedValueSecondaryColor(
a.getColor(
R.styleable.ComplicationDrawable_rangedValueSecondaryColor,
r.getColor(
R.color.complicationDrawable_rangedValueSecondaryColor, null)));
}
if (a.hasValue(R.styleable.ComplicationDrawable_highlightColor)) {
complicationStyle.setHighlightColor(
a.getColor(
R.styleable.ComplicationDrawable_highlightColor,
r.getColor(R.color.complicationDrawable_highlightColor, null)));
}
a.recycle();
}
/**
* Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
* for each ComplicationDrawable. Note that framework may have called this once to create the
* ComplicationDrawable instance from an XML resource.
*
* @param r Resources used to resolve attribute values
* @param parser XML parser from which to inflate this ComplicationDrawable
* @param attrs Base set of attribute values
* @param theme Ignored by ComplicationDrawable
*/
@Override
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
if (mIsInflatedFromXml) {
throw new IllegalStateException("inflate may be called once only.");
}
mIsInflatedFromXml = true;
int type;
final int outerDepth = parser.getDepth();
// Inflate attributes always shared between active and ambient mode
inflateAttributes(r, parser);
// Reset both style builders to default values
setStyleToDefaultValues(mActiveStyle, r);
setStyleToDefaultValues(mAmbientStyle, r);
// Attributes of the outer tag applies to both active and ambient styles
inflateStyle(false, r, parser);
inflateStyle(true, r, parser);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
// Attributes of inner <ambient> tag applies to ambient style only
final String name = parser.getName();
if (TextUtils.equals(name, "ambient")) {
inflateStyle(true, r, parser);
} else {
Log.w(
"ComplicationDrawable",
"Unknown element: " + name + " for ComplicationDrawable " + this);
}
}
}
/**
* Draws the complication for the last known time. Last known time is derived from
* ComplicationDrawable#setCurrentTimeMillis(long)}.
*
* @param canvas Canvas for the complication to be drawn onto
*/
@Override
public void draw(@NonNull Canvas canvas) {
assertInitialized();
updateStyleIfRequired();
mComplicationRenderer.draw(
canvas,
mCurrentTimeMillis,
mInAmbientMode,
mLowBitAmbient,
mBurnInProtection,
mHighlighted);
}
/** Does nothing. */
@Override
public void setAlpha(int alpha) {
// No op.
}
/**
* Does nothing. Use {@link ComplicationStyle#setImageColorFilter(ColorFilter)} instead to apply
* color filter to small and large images.
*/
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
// No op.
}
/**
* {@inheritDoc}
*
* @deprecated This method is no longer used in graphics optimizations
*/
@Override
@Deprecated
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
if (mComplicationRenderer != null) {
mComplicationRenderer.setBounds(bounds);
}
}
/**
* Sets the text to be rendered when {@link ComplicationData} is of type {@link
* ComplicationData#TYPE_NO_DATA}. If {@code noDataText} is null, an empty text will be
* rendered.
*/
public void setNoDataText(@Nullable CharSequence noDataText) {
if (noDataText == null) {
mNoDataText = "";
} else {
mNoDataText = noDataText.subSequence(0, noDataText.length());
}
if (mComplicationRenderer != null) {
mComplicationRenderer.setNoDataText(mNoDataText);
}
}
/**
* Sets if the ranged value progress should be hidden when {@link ComplicationData} is of type
* {@link ComplicationData#TYPE_RANGED_VALUE}.
*
* @param rangedValueProgressHidden {@code true} if progress should be hidden, {@code false}
* otherwise
* @attr ref androidx.wear.watchface.complications.rendering.R
* .styleable#ComplicationDrawable_rangedValueProgressHidden
*/
public void setRangedValueProgressHidden(boolean rangedValueProgressHidden) {
mRangedValueProgressHidden = rangedValueProgressHidden;
if (mComplicationRenderer != null) {
mComplicationRenderer.setRangedValueProgressHidden(rangedValueProgressHidden);
}
}
/** Returns {@code true} if the ranged value progress is hidden, {@code false} otherwise. */
public boolean isRangedValueProgressHidden() {
return mRangedValueProgressHidden;
}
/**
* Sets the complication data to be drawn. If {@code complicationData} is {@code null}, nothing
* will be drawn when {@link #draw(Canvas)} is called.
*/
public void setComplicationData(@Nullable ComplicationData complicationData) {
assertInitialized();
mComplicationRenderer.setComplicationData(complicationData);
}
/**
* Returns the {@link ComplicationData} to be drawn by this ComplicationDrawable.
*/
@Nullable
public ComplicationData getComplicationData() {
return mComplicationRenderer.getComplicationData();
}
/** Sets whether the complication should be rendered in ambient mode. */
public void setInAmbientMode(boolean inAmbientMode) {
mInAmbientMode = inAmbientMode;
}
/** Returns whether the complication is rendered in ambient mode. */
public boolean isInAmbientMode() {
return mInAmbientMode;
}
/**
* Sets whether the complication, when rendering in ambient mode, should apply a style suitable
* for low bit ambient mode.
*/
public void setLowBitAmbient(boolean lowBitAmbient) {
mLowBitAmbient = lowBitAmbient;
}
/**
* Returns whether the complication, when rendering in ambient mode, should apply a style
* suitable for low bit ambient mode.
*/
public boolean isLowBitAmbient() {
return mLowBitAmbient;
}
/**
* Sets whether the complication, when rendering in ambient mode, should apply a style suitable
* for display on devices with burn in protection.
*/
public void setBurnInProtection(boolean burnInProtection) {
mBurnInProtection = burnInProtection;
}
/**
* Whether the complication, when rendering in ambient mode, should apply a style suitable for
* display on devices with burn in protection.
*/
public boolean isBurnInProtectionOn() {
return mBurnInProtection;
}
/**
* Sets the current time in mulliseconds since the epoch. This will be used to render
* {@link ComplicationData} with time dependent text.
*
* @param currentTimeMillis time in milliseconds since the epoch
*/
public void setCurrentTimeMillis(long currentTimeMillis) {
mCurrentTimeMillis = currentTimeMillis;
}
/**
* Returns the time in milliseconds since the epoch used for rendering {@link ComplicationData}
* with time dependent text.
*/
public long getCurrentTimeMillis() {
return mCurrentTimeMillis;
}
/**
* Sets whether the complication is currently highlighted. This may be called by a watch face
* when a complication is tapped.
*
* <p>If watch face is in ambient mode, highlight will not be visible even if this is set to
* {@code true}, because it may cause burn-in or power inefficiency.
*/
public void setHighlighted(boolean isHighlighted) {
mHighlighted = isHighlighted;
}
/**
* Returns whether the complication is currently highlighted.
*/
public boolean isHighlighted() {
return mHighlighted;
}
/**
* Sends the tap action for the complication if tap coordinates are inside the complication
* bounds.
*
* <p>This method will also highlight the complication. The highlight duration is 300
* milliseconds by default but can be modified using the {@link #setHighlightDuration(long)}
* method.
*
* <p>If {@link ComplicationData} has the type {@link ComplicationData#TYPE_NO_PERMISSION}, this
* method will launch an intent to request complication permission for the watch face. This will
* only work if the context set by {@link #getDrawable} or the constructor is an
* instance of WatchFaceService.
*
* @param x X coordinate of the tap relative to screen origin
* @param y Y coordinate of the tap relative to screen origin
* @return {@code true} if the action was successful, {@code false} if complication data is not
* set, the complication has no tap action, the tap action (i.e. {@link
* android.app.PendingIntent}) is cancelled, or the given x and y are not inside the
* complication bounds.
*/
public boolean onTap(@Px int x, @Px int y) {
if (mComplicationRenderer == null) {
return false;
}
ComplicationData data = mComplicationRenderer.getComplicationData();
if (data == null) {
return false;
}
if (!data.hasTapAction() && data.getType() != ComplicationData.TYPE_NO_PERMISSION) {
return false;
}
if (!getBounds().contains(x, y)) {
return false;
}
if (data.getType() == ComplicationData.TYPE_NO_PERMISSION) {
// Check if mContext is an instance of WatchFaceService. We can't use the standard
// instanceof operator because WatchFaceService is defined in library which depends on
// this one, hence the reflection hack.
try {
if (Class.forName("androidx.wear.watchface.WatchFaceService")
.isInstance(mContext)) {
mContext.startActivity(
ComplicationHelperActivity.createPermissionRequestHelperIntent(
mContext,
new ComponentName(mContext, mContext.getClass()))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
return false;
}
} catch (ClassNotFoundException e) {
// If watchFaceServiceClass class isn't found we know mContext can't be an instance
// of WatchFaceService.
return false;
}
} else {
try {
data.getTapAction().send();
} catch (CanceledException e) {
return false;
}
}
if (getHighlightDuration() > 0) {
setHighlighted(true);
invalidateSelf();
mMainThreadHandler.removeCallbacks(mUnhighlightRunnable);
mMainThreadHandler.postDelayed(mUnhighlightRunnable, getHighlightDuration());
}
return true;
}
/**
* Sets the duration for the complication to stay highlighted after calling the {@link
* #onTap(int, int)} method. Default value is 300 milliseconds. Setting highlight duration to 0
* disables highlighting.
*
* @param highlightDurationMillis highlight duration in milliseconds
*/
public void setHighlightDuration(@IntRange(from = 0) long highlightDurationMillis) {
if (highlightDurationMillis < 0) {
throw new IllegalArgumentException("Highlight duration should be non-negative.");
}
mHighlightDuration = highlightDurationMillis;
}
/** Returns the highlight duration. */
public long getHighlightDuration() {
return mHighlightDuration;
}
/** Builds styles and syncs them with the complication renderer. */
void updateStyleIfRequired() {
if (mActiveStyle.isDirty() || mAmbientStyle.isDirty()) {
mComplicationRenderer.updateStyle(mActiveStyle, mAmbientStyle);
mActiveStyle.clearDirtyFlag();
mAmbientStyle.clearDirtyFlag();
}
}
/**
* Throws an exception if the context is not set. This method should be called if any of the
* member methods do a context-dependent job.
*/
private void assertInitialized() {
if (mContext == null) {
throw new IllegalStateException(
"ComplicationDrawable does not have a context. Use setContext(Context) to set"
+ " it first.");
}
}
/** Returns complication style for active mode. */
@NonNull
public ComplicationStyle getActiveStyle() {
return mActiveStyle;
}
/** Returns complication style for ambient mode. */
@NonNull
public ComplicationStyle getAmbientStyle() {
return mAmbientStyle;
}
/** Returns complication renderer. */
@Nullable
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public ComplicationRenderer getComplicationRenderer() {
return mComplicationRenderer;
}
@Nullable
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public CharSequence getNoDataText() {
return mNoDataText;
}
}