表盘可以通过setDefaultSystemComplicationProvider(int watchFaceComplicationId, int systemProvider, int type)
来设置要显示的系统复杂数据。
SystemProviders列举了目前系统支持的复杂数据。
package android.support.wearable.complications;
public class SystemProviders {
public static final int WATCH_BATTERY = 1;
public static final int DATE = 2;
public static final int TIME_AND_DATE = 3;
public static final int STEP_COUNT = 4;
public static final int WORLD_CLOCK = 5;
public static final int APP_SHORTCUT = 6;
public static final int UNREAD_NOTIFICATION_COUNT = 7;
public static final int ANDROID_PAY = 8;
public static final int NEXT_EVENT = 9;
public static final int RETAIL_STEP_COUNT = 10;
public static final int RETAIL_CHAT = 11;
public static final int SUNRISE_SUNSET = 12;
public static final int DAY_OF_WEEK = 13;
public static final int FAVORITE_CONTACT = 14;
public static final int MOST_RECENT_APP = 15;
public static final int DAY_AND_DATE = 16;
private static final String HOME_PACKAGE_NAME = "com.google.android.wearable.app";
private static final String BATTERY_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.BatteryProviderService";
private static final String DATE_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.DayOfMonthProviderService";
private static final String CURRENT_TIME_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.CurrentTimeProvider";
private static final String STEPS_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.StepsProviderService";
private static final String NEXT_EVENT_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.NextEventProviderService";
private static final String WORLD_CLOCK_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.WorldClockProviderService";
private static final String APPS_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.LauncherProviderService";
private static final String UNREAD_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.UnreadNotificationsProviderService";
private static final String RETAIL_STEPS_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.RetailStepsProviderService";
private static final String RETAIL_CHAT_CLASS_NAME = "com.google.android.clockwork.home.complications.providers.RetailChatProviderService";
private static final String PAY_PACKAGE_NAME = "com.google.android.apps.walletnfcrel";
private static final String PAY_CLASS_NAME = "com.google.commerce.tapandpay.android.wearable.complications.PayProviderService";
public SystemProviders() {
}
/** @deprecated */
@Deprecated
public static ComponentName batteryProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.BatteryProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName dateProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.DayOfMonthProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName currentTimeProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.CurrentTimeProvider");
}
/** @deprecated */
@Deprecated
public static ComponentName worldClockProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.WorldClockProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName appsProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.LauncherProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName stepCountProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.StepsProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName unreadCountProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.UnreadNotificationsProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName nextEventProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.NextEventProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName retailStepCountProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.RetailStepsProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName retailChatProvider() {
return new ComponentName("com.google.android.wearable.app", "com.google.android.clockwork.home.complications.providers.RetailChatProviderService");
}
/** @deprecated */
@Deprecated
public static ComponentName androidPayProvider() {
return new ComponentName("com.google.android.apps.walletnfcrel", "com.google.commerce.tapandpay.android.wearable.complications.PayProviderService");
}
@Retention(RetentionPolicy.SOURCE)
public @interface ProviderId {
}
}
用户自己实现复杂数据的方式,可参考https://developer.android.com/training/wearables/data-providers/exposing-data-complications。
复杂数据的刷新方式有三种:
1.主动刷新,也称为推送更新,是通过ProviderUpdateRequester来实现的。
ProviderUpdateRequester requester =
new ProviderUpdateRequester(context, new ComponentName(context, CurrentTimeProvider.class));
requester.requestUpdateAll();
2.在xml中指定默认刷新时间。
表盘处于活动状态时,会按照UPDATE_PERIOD_SECONDS
指定的频率刷新复杂数据,如果复杂功能中显示的信息不需要定期更新(例如当您使用推送更新时),请将该值设为 0
。如果您未将 UPDATE_PERIOD_SECONDS
设为 0
,则必须至少设为 300
(5 分钟),这是系统为了节省设备电池电量而执行的最短更新周期。此外,请注意,当设备处于微光模式或未佩戴时,更新请求的频率可能会降低。
3.一些依赖时效的字符串复杂数据,可以通过
ComplicationText.TimeFormatBuilder()来跟时间信息关联起来,时间更新时home会自动更新这个数据的显示,例如
String timePattern = (DateFormat.is24HourFormat(this)) ? "HH:mm" : "hh:mm";//12小时制也不显示AM PM
if (type == ComplicationData.TYPE_SHORT_TEXT) {
data = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(new ComplicationText.TimeFormatBuilder().setFormat(timePattern).
build())
.build();
} else {
Log.e(TAG, "onComplicationUpdate unsupport type " + type);
data = new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
.build();
}
ComplicationText的build()会根据是否有时间format(例如"yyyy-mm-dd hh:mm:ss"/"HH:MM am"等时间格式化字符串)来判断是否需要dependTime.
public ComplicationText build() {
if (this.mFormat == null) {
throw new IllegalStateException("Format must be specified.");
} else {
return new ComplicationText(this.mSurroundingText, new TimeFormatText(this.mFormat, this.mStyle, this.mTimeZone));
}
}
private ComplicationText(CharSequence surroundingText, TimeDependentText timeDependentText) {
this.mTemplateValues = new CharSequence[]{"", "^2", "^3", "^4", "^5", "^6", "^7", "^8", "^9"};
this.mSurroundingText = surroundingText;
this.mTimeDependentText = timeDependentText;
this.checkFields();
}
boolean isTimeDependent() {
return this.mTimeDependentText != null;
}
public static CharSequence getText(Context context, ComplicationText complicationText, long dateTimeMillis) {
return complicationText == null ? null : complicationText.getText(context, dateTimeMillis);
}
public CharSequence getText(Context context, long dateTimeMillis) {
if (this.mTimeDependentText == null) {
return this.mSurroundingText;
} else {
CharSequence timeDependentPart;
if (this.mDependentTextCache != null && this.mTimeDependentText.returnsSameText(this.mDependentTextCacheTime, dateTimeMillis)) {
timeDependentPart = this.mDependentTextCache;
} else {
timeDependentPart = this.mTimeDependentText.getText(context, dateTimeMillis);
this.mDependentTextCacheTime = dateTimeMillis;
this.mDependentTextCache = timeDependentPart;
}
if (this.mSurroundingText == null) {
return timeDependentPart;
} else {
this.mTemplateValues[0] = timeDependentPart;
return TextUtils.expandTemplate(this.mSurroundingText, this.mTemplateValues);
}
}
}
public interface TimeDependentText extends Parcelable {
CharSequence getText(Context context, long dateTimeMillis);
boolean returnsSameText(long firstDateTimeMillis, long secondDateTimeMillis);
long getNextChangeTime(long fromTime);
}
public long getNextChangeTime(long fromTime) {
return this.mTimeDependentText == null ? 9223372036854775807L : this.mTimeDependentText.getNextChangeTime(fromTime);
}
可以看出,ComplicationText获取显示内容时会传递一个时间戳,如果是
isTimeDependent返回true,也就是mTimeDependentText不为null,就会调用
TimeDependentText的getText(Context context, long dateTimeMillis)来获取显示内容,
TimeDependentText是一个interface,继续跟踪实现它的地方,在android.support.wearable.complications.TimeFormatText中实现了接口。
public class TimeFormatText implements TimeDependentText {
private static final String[][] DATE_TIME_FORMAT_SYMBOLS = new String[][]{{"S", "s"}, {"m"}, {"H", "K", "h", "k", "j", "J", "C"}, {"a", "b", "B"}};
private static final long[] DATE_TIME_FORMAT_PRECISION;
private final SimpleDateFormat mDateFormat;
private final int mStyle;
private final TimeZone mTimeZone;
private final Date mDate;
private long mTimePrecision;
public static final Creator CREATOR;
public TimeFormatText(String format, int style, TimeZone timeZone) {
this.mDateFormat = new SimpleDateFormat(format);
this.mStyle = style;
this.mTimePrecision = -1L;
if (timeZone != null) {
this.mDateFormat.setTimeZone(timeZone);
this.mTimeZone = timeZone;
} else {
this.mTimeZone = this.mDateFormat.getTimeZone();
}
this.mDate = new Date();
}
@SuppressLint({"DefaultLocale"})
public CharSequence getText(Context context, long dateTimeMillis) {
String formattedDate = this.mDateFormat.format(new Date(dateTimeMillis));
switch(this.mStyle) {
case 2:
return formattedDate.toUpperCase();
case 3:
return formattedDate.toLowerCase();
default:
return formattedDate;
}
}
public boolean returnsSameText(long firstDateTimeMillis, long secondDateTimeMillis) {
long precision = this.getPrecision();
firstDateTimeMillis += this.getOffset(firstDateTimeMillis);
secondDateTimeMillis += this.getOffset(secondDateTimeMillis);
return firstDateTimeMillis / precision == secondDateTimeMillis / precision;
}
public long getNextChangeTime(long fromTime) {
long precision = this.getPrecision();
long offset = this.getOffset(fromTime);
return ((fromTime + offset) / precision + 1L) * precision - offset;
}
//省略......
}
可以看出在getText时,还可以通过制定mStyle来选择是否大小写显示。默认是1小写。
开发阶段遇到两个问题,一个是设置时区后TimeDepend复杂数据未更新,另一个是在Offload模式下,小时分钟显示错误,一般是误差5分钟的整数倍。
在开发中,我自己实现了一个TimeDenpend的复杂数据,但发现修改时区后表盘信息并未刷新,但用系统提供的SystemProviders.TIME_AND_DATE是可以根据时区变化来刷新数据的。猜测Home会对自己提供的复杂数据特殊处理,例如时区变化时刷新时钟相关的复杂数据,跟踪WearOS Home一探究性。
以
CurrentTimeProvider为例
lingx@ubuntu:~/work/vendor/google_clockwork_partners/packages/ClockworkHome$ grep -nr 'CurrentTimeProvider' *
AndroidManifest.xml:1202: android:name="com.google.android.clockwork.home.complications.providers.CurrentTimeProvider"
Binary file java/com/google/android/clockwork/home/module/complications/.ComplicationProvidersModule.java.swp matches
java/com/google/android/clockwork/home/module/complications/ComplicationProvidersModule.java:13:import com.google.android.clockwork.home.complications.providers.CurrentTimeProvider;
java/com/google/android/clockwork/home/module/complications/ComplicationProvidersModule.java:101: new ProviderUpdateRequester(context, new ComponentName(context, CurrentTimeProvider.class));
java/com/google/android/clockwork/home/complications/ProviderGetter.java:41: "com.google.android.clockwork.home.complications.providers.CurrentTimeProvider";
java/com/google/android/clockwork/home/complications/providers/CurrentTimeProvider.java:8:public class CurrentTimeProvider extends ComplicationProviderService {
java/com/google/android/clockwork/home/complications/providers/CurrentTimeDataBuilder.java:9:/** Creates complication data for use by the {@link CurrentTimeProvider}. */
java/com/google/android/clockwork/home/complications/SystemProviderMappingImpl.java:24: "com.google.android.clockwork.home.complications.providers.CurrentTimeProvider";
lingx@ubuntu:~/work/vendor/google_clockwork_partners/packages/ClockworkHome$
简单了解下SystemProviderMappingImpl、ProviderGetter.java、ComplicationProvidersModule.java。
其中SystemProviderMappingImpl做了一个映射,供SystemProviders使用。
public class SystemProviderMappingImpl implements SystemProviderMapping {
private static final String HOME_PACKAGE_NAME = "com.google.android.wearable.app";
private static final String BATTERY_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.BatteryProviderService";
private static final String DATE_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.DayOfMonthProviderService";
private static final String DAY_AND_DATE_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.DateDayOfWeekProviderService";
private static final String DAY_OF_WEEK_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.DayOfWeekProviderService";
private static final String CURRENT_TIME_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.CurrentTimeProvider";
private static final String STEPS_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.StepsProviderService";
private static final String NEXT_EVENT_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.NextEventProviderService";
private static final String SUNRISE_SUNSET_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.SunriseSunsetProviderService";
private static final String WORLD_CLOCK_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.WorldClockProviderService";
private static final String APPS_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.LauncherProviderService";
private static final String MOST_RECENT_APP_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.MostRecentAppProviderService";
private static final String UNREAD_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers"
+ ".UnreadNotificationsProviderService";
private static final String RETAIL_STEPS_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.RetailStepsProviderService";
private static final String RETAIL_CHAT_CLASS_NAME =
"com.google.android.clockwork.home.complications.providers.RetailChatProviderService";
private static final String PAY_PACKAGE_NAME = "com.google.android.apps.walletnfcrel";
private static final String PAY_CLASS_NAME =
"com.google.commerce.tapandpay.android.wearable.complications.PayProviderService";
private static final String FAVORITE_CONTACT_CLASS_NAME =
"com.google.android.clockwork.home.contacts.ContactsComplicationProviderService";
@Nullable
@Override
public ComponentName getSystemProviderComponent(@ProviderId int systemProvider) {
switch (systemProvider) {
case SystemProviders.WATCH_BATTERY:
return new ComponentName(HOME_PACKAGE_NAME, BATTERY_CLASS_NAME);
case SystemProviders.DATE:
return new ComponentName(HOME_PACKAGE_NAME, DATE_CLASS_NAME);
case SystemProviders.DAY_AND_DATE:
return new ComponentName(HOME_PACKAGE_NAME, DAY_AND_DATE_CLASS_NAME);
case SystemProviders.DAY_OF_WEEK:
return new ComponentName(HOME_PACKAGE_NAME, DAY_OF_WEEK_CLASS_NAME);
case SystemProviders.TIME_AND_DATE:
return new ComponentName(HOME_PACKAGE_NAME, CURRENT_TIME_CLASS_NAME);
case SystemProviders.STEP_COUNT:
return new ComponentName(HOME_PACKAGE_NAME, STEPS_CLASS_NAME);
case SystemProviders.WORLD_CLOCK:
return new ComponentName(HOME_PACKAGE_NAME, WORLD_CLOCK_CLASS_NAME);
case SystemProviders.APP_SHORTCUT:
return new ComponentName(HOME_PACKAGE_NAME, APPS_CLASS_NAME);
case SystemProviders.MOST_RECENT_APP:
return new ComponentName(HOME_PACKAGE_NAME, MOST_RECENT_APP_CLASS_NAME);
case SystemProviders.UNREAD_NOTIFICATION_COUNT:
return new ComponentName(HOME_PACKAGE_NAME, UNREAD_CLASS_NAME);
case SystemProviders.NEXT_EVENT:
return new ComponentName(HOME_PACKAGE_NAME, NEXT_EVENT_CLASS_NAME);
case SystemProviders.RETAIL_STEP_COUNT:
return new ComponentName(HOME_PACKAGE_NAME, RETAIL_STEPS_CLASS_NAME);
case SystemProviders.RETAIL_CHAT:
return new ComponentName(HOME_PACKAGE_NAME, RETAIL_CHAT_CLASS_NAME);
case SystemProviders.ANDROID_PAY:
return new ComponentName(PAY_PACKAGE_NAME, PAY_CLASS_NAME);
case SystemProviders.SUNRISE_SUNSET:
return new ComponentName(HOME_PACKAGE_NAME, SUNRISE_SUNSET_CLASS_NAME);
case SystemProviders.FAVORITE_CONTACT:
return new ComponentName(HOME_PACKAGE_NAME, FAVORITE_CONTACT_CLASS_NAME);
default:
return null;
}
}
}
ProviderGetter.java是给ProviderChooserController提供接口的,并会检查复杂数据的包名,复杂数据配置时,会启动
ComplicationHelperActivity,ComplicationHelperActivity会启动ProviderChooserController来选择复杂数据。
863
868
869
870
871
872
873
与复杂数据刷新相关的,重点要看的是ComplicationProvidersModule.java
package com.google.android.clockwork.home.module.complications;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.MainThread;
import android.support.wearable.complications.ProviderUpdateRequester;
import com.google.android.clockwork.common.io.IndentingPrintWriter;
import com.google.android.clockwork.home.complications.DefaultComplicationManager;
import com.google.android.clockwork.home.complications.providers.BatteryProviderService;
import com.google.android.clockwork.home.complications.providers.CurrentMediaProviderService;
import com.google.android.clockwork.home.complications.providers.CurrentTimeProvider;
import com.google.android.clockwork.home.complications.providers.DayAndDateProviderService;
import com.google.android.clockwork.home.complications.providers.DayOfMonthProviderService;
import com.google.android.clockwork.home.complications.providers.DayOfWeekProviderService;
import com.google.android.clockwork.home.complications.providers.LauncherProviderService;
import com.google.android.clockwork.home.complications.providers.MostRecentAppProviderService;
import com.google.android.clockwork.home.complications.providers.SunriseSunsetProviderService;
import com.google.android.clockwork.home.complications.providers.UnreadNotificationsProviderService;
import com.google.android.clockwork.home.events.BatteryChargeStateEvent;
import com.google.android.clockwork.home.events.MediaChangeEvent;
import com.google.android.clockwork.home.events.NotificationCountEvent;
import com.google.android.clockwork.home.events.PackageChangedEvent;
import com.google.android.clockwork.home.events.TimeZoneChangedEvent;
import com.google.android.clockwork.home.moduleframework.BasicModule;
import com.google.android.clockwork.home.moduleframework.ModuleBus;
import com.google.android.clockwork.home.moduleframework.eventbus.Subscribe;
import com.google.android.libraries.performance.primes.tracing.PrimesTrace;
@MainThread
public final class ComplicationProvidersModule implements BasicModule {
private final Context context;
private ModuleBus moduleBus;
private final DefaultComplicationManager complicationManager;
private int unreadStreamItemCount = -1;
public ComplicationProvidersModule(Context context) {
this.context = context;
complicationManager = DefaultComplicationManager.INSTANCE.get(context);
}
@Override
@PrimesTrace("ComplicationProvidersModule.initialize")
public void initialize(ModuleBus moduleBus) {
this.moduleBus = moduleBus;
this.moduleBus.register(this);
}
@Override
public void destroy() {}
@Subscribe
public void onBatteryChargeState(BatteryChargeStateEvent ev) {
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, BatteryProviderService.class));
requester.requestUpdateAll();
}
@Subscribe
public void onNotificationCount(NotificationCountEvent ev) {
if (ev.getUnreadCount() == unreadStreamItemCount) {
return;
}
unreadStreamItemCount = ev.getUnreadCount();
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, UnreadNotificationsProviderService.class));
requester.requestUpdateAll();
}
@Subscribe
public void onMediaChange(MediaChangeEvent ev) {
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, CurrentMediaProviderService.class));
requester.requestUpdateAll();
}
@Subscribe
public void onTimeZoneChanged(TimeZoneChangedEvent ev) {
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, DayOfMonthProviderService.class));
requester.requestUpdateAll();
requester =
new ProviderUpdateRequester(
context, new ComponentName(context, DayOfWeekProviderService.class));
requester.requestUpdateAll();
requester =
new ProviderUpdateRequester(
context, new ComponentName(context, DayAndDateProviderService.class));
requester.requestUpdateAll();
requester =
new ProviderUpdateRequester(context, new ComponentName(context, CurrentTimeProvider.class));
requester.requestUpdateAll();
requester =
new ProviderUpdateRequester(
context, new ComponentName(context, SunriseSunsetProviderService.class));
requester.requestUpdateAll();
}
@Subscribe
public void onPackageStatusChanged(PackageChangedEvent ev) {
// For LauncherProviderService.
if (Intent.ACTION_PACKAGE_REMOVED.equals(ev.getAction())) {
if (!ev.isReplacing()) {
complicationManager.removeConfigForPackage(ev.getPackageName());
}
// Requests an update for LauncherProviderService.
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, LauncherProviderService.class));
requester.requestUpdateAll();
}
// For MostRecentAppProviderService
if (!Intent.ACTION_PACKAGE_REMOVED.equals(ev.getAction()) || !ev.isReplacing()) {
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, new ComponentName(context, MostRecentAppProviderService.class));
requester.requestUpdateAll();
}
}
@Override
public void dumpState(IndentingPrintWriter ipw, boolean verbose) {
complicationManager.dumpState(ipw, verbose);
}
}
可见,home监听了时区、电量状态、消息提醒、播放状态信息,来主动刷新SystemProviders。
比较合理的做法应该是home检测到时区变化,然后刷新复杂数据,理论上所有timedepend的复杂数据都应该刷新,无论是系统提供的还是用户自定义的,看来目前时区变化后home只刷了系统复杂数据,没有去刷别的复杂数据,这就需要用户自己监听时区变化再刷新复杂数据。
public class TimeZoneReceiver extends BroadcastReceiver {
private static final String TAG = "TimeZoneReceiver";
private void freshAmbientComplications(Context context) {
ProviderUpdateRequester requester =
new ProviderUpdateRequester(
context, xxxx);
requester.requestUpdateAll();
}
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive " + intent);
freshAmbientComplications(context);
}
}
offload模式有BG绘制,其他模式是由AP绘制的。在高通3100的MCU架构中,为了省电,使用了多cpu交互模式。如下图所示,BG和AP是两个不同的cpu核儿。其中Application process跑WearOS系统,sensor算法主要跑在MDSP上,BlackGhost是BG的全称,是一个主频较低功耗较低的cpu。
可以看出AP绘制和BG绘制是在不同的cpu。
现在遇到的问题是BG绘制CurrentTimeProvider的数据时,显示的"小时:分钟",有概率出现比当前时间早5分钟的整数倍的时间信息。AP测的绘制是没有出现问题的。
先看下复杂数据provider的实现:
public class CurrentTimeProvider extends ComplicationProviderService {
@Override
public void onComplicationUpdate(int complicationId, int type, ComplicationManager manager) {
CurrentTimeDataBuilder dataBuilder =
new CurrentTimeDataBuilder(Locale.getDefault(), DateFormat.is24HourFormat(this));
manager.updateComplicationData(complicationId, dataBuilder.buildComplicationData(type));
}
}
final class CurrentTimeDataBuilder {
private static final String TAG = "CurrentTimeBuilder";
private final Locale locale;
private final boolean use24Hour;
CurrentTimeDataBuilder(Locale locale, boolean use24Hour) {
this.locale = locale;
this.use24Hour = use24Hour;
}
ComplicationData buildComplicationData(int type) {
ComplicationData data;
switch (type) {
case ComplicationData.TYPE_SHORT_TEXT:
data = buildCurrentTimeData();
break;
default:
data = new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build();
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unexpected complication type " + type);
}
}
return data;
}
private ComplicationData buildCurrentTimeData() {
ComplicationData data;
String timePattern = (DateFormat.is24HourFormat(this)) ? "HH:mm" : "hh:mm";//12小时制也不显示AM PM
data =
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(new ComplicationText.TimeFormatBuilder().setFormat(timePattern).build())
.build();
return data;
}
}
AP测的绘制逻辑在DecompositionDrawable中实现。
public class DecompositionDrawable extends Drawable {
......
public DecompositionDrawable(Context context) {
this.context = context;
}
public void draw(Canvas canvas) {
if (this.decomposition != null) {
Rect bounds = this.getBounds();
if (this.clipToCircle) {
canvas.save();
canvas.clipPath(this.roundPath);
}
this.converter.setPixelBounds(bounds);
Iterator var3 = this.drawnComponents.iterator();
while(true) {
DrawnComponent component;
do {
do {
if (!var3.hasNext()) {
if (this.inConfigMode) {
canvas.drawColor(this.context.getColor(color.config_scrim_color));
var3 = this.drawnComponents.iterator();
while(var3.hasNext()) {
component = (DrawnComponent)var3.next();
if (component instanceof ComplicationComponent) {
this.drawComplication((ComplicationComponent)component, canvas, this.converter);
}
}
}
if (this.clipToCircle) {
canvas.restore();
}
return;
}
component = (DrawnComponent)var3.next();
} while(this.inAmbientMode && !component.isAmbient());
} while(!this.inAmbientMode && !component.isInteractive());
if (component instanceof ImageComponent) {
this.drawImage((ImageComponent)component, canvas, this.converter);
} else if (component instanceof NumberComponent) {
this.drawNumber((NumberComponent)component, canvas, this.converter);
} else if (!this.inConfigMode && component instanceof ComplicationComponent) {
this.drawComplication((ComplicationComponent)component, canvas, this.converter);
}
}
}
}
private void drawComplication(ComplicationComponent component, Canvas canvas, CoordConverter converter) {
ComplicationDrawable drawable = (ComplicationDrawable)this.complicationDrawables.get(component.getWatchFaceComplicationId());
drawable.setCurrentTimeMillis(this.currentTimeMillis);
drawable.setInAmbientMode(this.inAmbientMode);
drawable.setBurnInProtection(this.burnInProtection);
drawable.setLowBitAmbient(this.lowBitAmbient);
RectF proportionalBounds = component.getBounds();
if (proportionalBounds != null) {
converter.getPixelRectFromProportional(proportionalBounds, this.boundsRect);
drawable.setBounds(this.boundsRect);
}
drawable.draw(canvas);
}
private void drawNumber(NumberComponent numberComponent, Canvas canvas, CoordConverter converter) {
if (!this.inAmbientMode || numberComponent.getMsPerIncrement() >= TimeUnit.MINUTES.toMillis(1L)) {
DigitDrawable digitDrawable = (DigitDrawable)this.fontDrawables.get(numberComponent.getFontComponentId());
if (digitDrawable != null) {
String digitString = numberComponent.getDisplayStringForTime(this.currentTimeMillis);
int maxDigits = (int)Math.log10((double)numberComponent.getHighestValue()) + 1;
PointF position = numberComponent.getPosition();
int digitWidth = digitDrawable.getIntrinsicWidth();
int digitHeight = digitDrawable.getIntrinsicHeight();
int x = converter.getPixelX(position.x);
x += digitWidth * (maxDigits - 1);
int y = converter.getPixelY(position.y);
this.boundsRect.set(x, y, x + digitWidth, y + digitHeight);
for(int i = digitString.length() - 1; i >= 0; --i) {
digitDrawable.setBounds(this.boundsRect);
digitDrawable.setCurrentDigit(Character.digit(digitString.charAt(i), 10));
digitDrawable.draw(canvas);
this.boundsRect.offset(-digitWidth, 0);
}
}
}
}
private void drawImage(ImageComponent imageComponent, Canvas canvas, CoordConverter converter) {
RotateDrawable drawable = (RotateDrawable)this.imageDrawables.get(imageComponent.getImage());
if (drawable != null) {
if (!this.inAmbientMode || imageComponent.getDegreesPerDay() < 518400.0F) {
converter.getPixelRectFromProportional(imageComponent.getBounds(), this.boundsRect);
drawable.setBounds(this.boundsRect);
float angle = this.angleForTime(imageComponent.getOffsetDegrees(), imageComponent.getDegreesPerDay());
angle = this.angleWithStep(angle, imageComponent.getDegreesPerStep());
drawable.setFromDegrees(angle);
drawable.setToDegrees(angle);
if (angle > 0.0F) {
drawable.setPivotX((float)(converter.getPixelX(imageComponent.getPivot().x) - this.boundsRect.left));
drawable.setPivotY((float)(converter.getPixelY(imageComponent.getPivot().y) - this.boundsRect.top));
}
drawable.setLevel(drawable.getLevel() + 1);
drawable.draw(canvas);
}
}
}
.........
}
由于Offload模式下显示的特征,是不能直接显示字符串的,只能显示有限的component,包括ComplicationComponent、FontComponent、NumberComponent、ImageComponent。要显示字符串如"3月16 周一",就需要把字符串build成相应的component,跟踪WearOs Home源码发现,是将字符串转成了FontComponent+NumberComponent,这些component都是drawable,然后通过SPI传给BG(SidekickManager),由BG来负责显示。目前看不到BG测的代码,只能先分析AP端的,看下com.google.android.clockwork.home.watchfaces.OffloadControllerImpl.java和WatchFaceController、WatchFaceModule。
DecomposableWatchFace通过
updateDecomposition(@Nullable WatchFaceDecomposition decomposition)
会调用WatchFaceController的updateDecomposition来通知home。
/**
* Abstraction for controlling watch faces that are implemented as wallpapers. Hides from the
* Activity all interaction with the wallpaper.
*/
public class WatchFaceController implements Dumpable {
.......
@Override
public void updateDecomposition(@Nullable WatchFaceDecomposition decomposition) {
receivedDecomposition = true;
/*
* The config is set to indicate whether the current set watchface is decomposable. Other
* parts of the system such as Settings need to know whether the current watchface is
* decomposable or not, but such information is only known to Home. By setting this property,
* we pass this information to other system apps, see details in b/134506713.
*/
ambientConfig.setCurrentWatchfaceDecomposable(true);
if (decomposition == null) {
exitAmbientOffload();
offloadController.clearDecomposition();
} else {
offloadController.sendDecomposition(
decomposition,
success -> {
if (success && watchFaceVisibility.isInAmbient()) {
offloadController.enterAmbient(maybeStartOffloadRunnable);
}
});
}
}
}
.......
}
分析下offloadController.sendDecomposition(
decomposition,
success -> {
if (success && watchFaceVisibility.isInAmbient()) {
offloadController.enterAmbient(maybeStartOffloadRunnable);
}
});
sendDecomposition实现的地方在OffloadControllerImpl
/** Controls sending watch face decompositions to sidekick. */
public class OffloadControllerImpl implements OffloadController {
OffloadControllerImpl(
Context context,
SidekickManagerAsync sidekickManager,
ScreenConfiguration screenConfig,
BurnInConfig burnInConfig,
OffscreenRenderer offscreenRenderer,
Clock clock) {
this.context = context;
this.sidekickManager = checkNotNull(sidekickManager);//
this.burnInConfig = burnInConfig;//是否使用防烧屏。
this.offscreenRenderer = checkNotNull(offscreenRenderer);
this.clock = clock;
this.updateScheduler = new AmbientUpdateScheduler(context, clock);
coordConverter = new CoordConverter();
coordConverter.setPixelBounds(0, 0, screenConfig.getWidthPx(), screenConfig.getHeightPx());
}
@Override
public void sendDecomposition(
WatchFaceDecomposition decomposition, @Nullable SidekickManagerAsync.Callback callback) {
synchronized (lock) {
if (sendingTwmWatchFaceActivatesTwm() && receivedTwmDecomposition) {
return;
}
saveAmbientComplications(decomposition);
com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition decomposition2 =
convertAmbientDecomposition(decomposition, false);
if (decomposition2 != null) {
sidekickManager.sendWatchFace(decomposition2, false, callback);
sentDecomposition = true;
} else {
clearDecomposition();
}
}
}
@Override
public boolean enterAmbient(@Nullable Runnable onUpdatesSent) {
synchronized (lock) {
if (!sentDecomposition) {
return false;
}
inAmbientOffload = true;
updateScheduler.setInAmbientOffload(true);
long currentTime = clock.getCurrentTimeMs();
long nextMinute = nextMinuteBoundary(currentTime);
com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition.Builder builder =
new com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition.Builder();
boolean needReplace = false;
for (int i = 0; i < complications.size(); i++) {
Complication complication = complications.valueAt(i);
complication.updateTask.setNextUpdateTimeMs(nextMinute);
if (complication.needsDraw) {
addComplicationComponents(builder, complication, currentTime);
needReplace = true;
}
}
if (overlayRedraw) {
overlayUpdateTask.setNextUpdateTimeMs(nextMinute);
builder.addImageComponents(convertImageComponent(overlayComponent));
needReplace = true;
overlayRedraw = false;
}
if (needReplace) {
sidekickManager.replaceWatchFaceComponents(
builder.build(), onUpdatesSent == null ? null : success -> onUpdatesSent.run());
} else if (onUpdatesSent != null) {
onUpdatesSent.run();
}
}
return true;
}
private void saveAmbientComplications(WatchFaceDecomposition decomposition) {
synchronized (lock) {
complications.clear();
int currentComponentId = WatchFaceDecomposition.MAX_COMPONENT_ID + COMPONENT_ID_STEP;
for (ComplicationComponent component : decomposition.getComplicationComponents()) {
if (!component.isAmbient()) {
continue;
}
ComplicationDrawable drawable;
ComplicationDrawable providedDrawable = component.getComplicationDrawable();
if (providedDrawable == null) {
drawable = new ComplicationDrawable();
} else {
drawable = new ComplicationDrawable(providedDrawable);
}
// Set low bit ambient to disable anti-aliasing.
drawable.setContext(context);
drawable.setInAmbientMode(true);
drawable.setLowBitAmbient(true);
drawable.setBurnInProtection(burnInConfig.isProtectionEnabled());
coordConverter.getPixelRectFromProportional(component.getBounds(), workingRect);
workingRect.offset(-workingRect.left, -workingRect.top);
drawable.setBounds(workingRect);
Complication complication = new Complication(component, drawable, currentComponentId);
int wfComplicationId = component.getWatchFaceComplicationId();
complication.callback = new ComplicationCallback(wfComplicationId);
complication.drawable.setCallback(complication.callback);
ComplicationData data = complicationDatas.get(wfComplicationId);
if (data != null) {
complication.drawable.setComplicationData(data);
complication.needsDraw = true;
}
complication.updateTask =
updateScheduler.createTask(() -> doInvalidateComplication(complication));
//updateTask刷新表盘的一个循环task。
complications.put(wfComplicationId, complication);
currentComponentId += COMPONENT_ID_STEP;
}
}
private void addComplicationComponents(
com.google.android.clockwork.decomposablewatchface.WatchFaceDecomposition.Builder builder,
Complication complication,
long currentTime) {
synchronized (lock) {
TimeDependentStrip strip = null;
int wfCompId = complication.component.getWatchFaceComplicationId();
ComplicationData data = complicationDatas.get(wfCompId);
if (data != null && data.isTimeDependent() && complicationStripSize >= 3) {
strip =
createRenderedComplicationFontStrip(complication, currentTime, complicationStripSize);
}
if (strip != null) {
builder.addFontComponents(strip.fontComponent);
builder.addNumberComponents(strip.numberComponent);
builder.addImageComponents(createBlankComponentForComplication(complication.component));
scheduleComplicationUpdateIfNeeded(complication, strip.lastUpdateTime);
} else {
strip = createDummyComplicationFontStrip(complication.component);
builder.addFontComponents(strip.fontComponent);
builder.addNumberComponents(strip.numberComponent);
builder.addImageComponents(
createRenderedComponentForComplication(complication, currentTime));
scheduleComplicationUpdateIfNeeded(complication, currentTime);
}
complication.needsDraw = false;
}
}
private TimeDependentStrip createRenderedComplicationFontStrip(
Complication complication, long currentTime, int size) {
synchronized (lock) {
ComplicationComponent component = complication.component;
ComplicationData data = complicationDatas.get(component.getWatchFaceComplicationId());
long[] frameTime = new long[size + 1];
int i;
// Fill in the update times and perform some basic sanity checks.
// frameTime[0] = current time
// frameTime[1] = first update
// ...
// frameTime[size - 1] = last frame
// frameTime[size] = scheduled invalidation
frameTime[0] = currentTime;
for (i = 1; i <= size; i++) {
frameTime[i] = determineNextChangeTime(data, frameTime[i - 1]);
if (frameTime[i] >= Long.MAX_VALUE
|| frameTime[i] <= frameTime[i - 1]
|| !data.isActive(frameTime[i - 1])) {
// We won't be able to properly schedule an update after the (i-1)'th frame. Stop there.
size = i - 1;
}
}
if (size < 3) {
// Not enough frames to determine update period
return null;
}
long updatePeriod = frameTime[2] - frameTime[1];
if (updatePeriod >= TimeUnit.DAYS.toMillis(1)) {
// Let's save some memory and use normal scheduling instead.
return null;
}
// Check the uniformity of updates
for (i = 1; i <= size; i++) {
long distance = frameTime[i] - frameTime[i - 1];
if (distance > updatePeriod || (distance < updatePeriod && i != 1 && i != size)) {
// It's fine only for the first update and the frame after the last one to come earlier.
size = i - 1;
}
}
if (size < 3) {
// Using a font strip with less than three frames is pointless
return null;
}
// Proceed to rendering the frames
RectF bounds = component.getBounds();
coordConverter.getPixelRectFromProportional(bounds, workingRect);
int width = workingRect.width();
int height = workingRect.height();
Bitmap partBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Bitmap stripBitmap = Bitmap.createBitmap(width, height * size, Config.ARGB_8888);
Canvas partCanvas = new Canvas(partBitmap);
Canvas stripCanvas = new Canvas(stripBitmap);
stripCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (i = 0; i < size; i++) {
partCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
complication.drawable.draw(partCanvas, frameTime[i]);
stripCanvas.drawBitmap(partBitmap, 0, height * i, null);
}
partBitmap.recycle();
long startTime = frameTime[1] - updatePeriod;
startTime += TimeZone.getDefault().getOffset(startTime);
return new TimeDependentStrip(
new com.google.android.clockwork.decomposablewatchface.FontComponent.Builder()
.setComponentId(complication.componentId + 2)
.setImage(Icon.createWithBitmap(stripBitmap))
.setDigitCount(size)
.build(),
new com.google.android.clockwork.decomposablewatchface.NumberComponent.Builder()
.setComponentId(complication.componentId + 1)
.setZOrder(component.getZOrder())
.setFontComponentId(complication.componentId + 2)
.setMsPerIncrement(updatePeriod)
.setLowestValue(0)
.setHighestValue(size - 1)
.setPosition(new PointF(bounds.left, bounds.top))
.setTimeOffsetMs(-startTime)
.setMinDigitsShown(0)
.build(),
frameTime[size - 1]);
}
}
private void scheduleComplicationUpdateIfNeeded(Complication complication, long currentTime) {
synchronized (lock) {
int wfCompId = complication.component.getWatchFaceComplicationId();
ComplicationData data = complicationDatas.get(wfCompId);
long nextChangeTime = determineNextChangeTime(data, currentTime);
if (nextChangeTime < Long.MAX_VALUE) {
complication.updateTask.setNextUpdateTimeMs(nextChangeTime);
complication.updateTask.schedule();
}
}
}
}
}
对于time dependent的complication data,是一次获取最近5次更新需要展示的内容,build成相应的component。
WatchFaceModule也监听了,并且调用了invalidateTimeDependentComplications,会刷新显示,但没有刷新复杂数据,所以显示的时间还是修改时区之前的。home里有个ComplicationProvidersModule.java,这里监听了时区,时区变化时,是主动推送了系统的复杂数据刷新,因此我们自己实现的复杂数据要自己处理时区刷新。
OffloadControllerImpl,显示复杂数据的时候,是把数据转成了FontComponent和NumberComponent,现在默认是5个长度,5个周期结束后,会通过complication.updateTask(doInvalidateComplication)重新刷新数据。FontComponent和NumberComponent的刷新,就跟之前纯字体的表盘一样了,根据高度去抠图显示字符。
另外offload模式能不能按秒刷新。指针是可以按秒刷的,而且1秒能刷10帧,看上去就像平扫。但是复杂数据不能按秒刷。我看home的代码 是以分钟为单位,测了一下,offload只能在整分钟刷复杂数据,普通的ImageComponent是可以每秒刷的,还可以平扫,但复杂数据,按照现在的home只能整分时刷,因为在AP判断重新读取复杂数据的时间转成了分钟的整数倍
private long determineNextChangeTime(ComplicationData data, long currentTime) {
if (data == null || !data.isTimeDependent()) {
return Long.MAX_VALUE;
}
long result = Long.MAX_VALUE;
result = getMinChangeTime(data.getShortText(), result, currentTime);
result = getMinChangeTime(data.getLongText(), result, currentTime);
result = getMinChangeTime(data.getShortTitle(), result, currentTime);
result = getMinChangeTime(data.getLongTitle(), result, currentTime);
return Math.max(result, nextMinuteBoundary(currentTime));
}
private long getMinChangeTime(ComplicationText text, long minTime, long currentTime) {
if (text == null) {
return minTime;
}
return Math.min(minTime, text.getNextChangeTime(currentTime));
}
private static long nextMinuteBoundary(long fromTime) {
return ((fromTime / MINUTES.toMillis(1)) + 1) * MINUTES.toMillis(1);
}
而对于NumberComponent、ImageComponent可以实现按秒刷新,最高频率约每秒10帧。
复杂数据配置信息记录在/data/data/com.google.android.wearable.app/databases/complications.db
可以导出该文件进行查看,ubuntu系统下使用sqlitebrowser预览db文件
1.安装可视化工具
#sudo apt-get install sqlitebrowser
2.导出android目录下的settings.db数据库
#adb pull /data/data/com.android.providers.settings/databases/settings.db .
3.用sqlitebrowser打开settings.db
#sqlitebrowser settings.db
例如:
删除/data/data/com.google.android.wearable.app/databases/complications.db文件,可以使得所有表盘的复杂数据配置恢复到默认状态。
表盘配置activity可以用过启动,ComplicationHelperActivity来选择复杂数据,例如
startActivityForResult(ComplicationHelperActivity.createProviderChooserHelperIntent(getApplicationContext(),
mWatchFaceComponentName, mSelectedComplicateId, supportedTypes), COMPLICATION_REQUEST_CODE);
public static Intent createProviderChooserIntent(ComponentName watchFace, int watchFaceComplicationId, int... supportedTypes) {
Intent intent = new Intent("com.google.android.clockwork.home.complications.ACTION_CHOOSE_PROVIDER");
intent.putExtra("android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT_NAME", watchFace);
intent.putExtra("android.support.wearable.complications.EXTRA_COMPLICATION_ID", watchFaceComplicationId);
intent.putExtra("android.support.wearable.complications.EXTRA_SUPPORTED_TYPES", supportedTypes);
return intent;
}
public class ComplicationHelperActivity extends Activity implements OnRequestPermissionsResultCallback {
public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE = "android.support.wearable.complications.ACTION_REQUEST_UPDATE_ALL_ACTIVE";
public static final String EXTRA_WATCH_FACE_COMPONENT = "android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT";
private static final String ACTION_START_PROVIDER_CHOOSER = "android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER";
private static final String ACTION_PERMISSION_REQUEST_ONLY = "android.support.wearable.complications.ACTION_PERMISSION_REQUEST_ONLY";
private static final String UPDATE_REQUEST_RECEIVER_PACKAGE = "com.google.android.wearable.app";
private static final int START_REQUEST_CODE_PROVIDER_CHOOSER = 1;
private static final int PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER = 1;
private static final int PERMISSION_REQUEST_CODE_REQUEST_ONLY = 2;
private static final String COMPLICATIONS_PERMISSION = "com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA";
private static final String COMPLICATIONS_PERMISSION_PRIVILEGED = "com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA_PRIVILEGED";
private ComponentName mWatchFace;
private int mWfComplicationId;
private int[] mTypes;
protected void onCreate(Bundle savedInstanceState) {
this.setTheme(16973840);
super.onCreate(savedInstanceState);
Intent intent = this.getIntent();
String var3 = intent.getAction();
byte var4 = -1;
switch(var3.hashCode()) {
case -121457581:
if (var3.equals("android.support.wearable.complications.ACTION_PERMISSION_REQUEST_ONLY")) {
var4 = 1;
}
break;
case 1414879715:
if (var3.equals("android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER")) {
var4 = 0;
}
}
switch(var4) {
case 0:
this.mWatchFace = (ComponentName)intent.getParcelableExtra("android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT_NAME");
this.mWfComplicationId = intent.getIntExtra("android.support.wearable.complications.EXTRA_COMPLICATION_ID", 0);
this.mTypes = intent.getIntArrayExtra("android.support.wearable.complications.EXTRA_SUPPORTED_TYPES");
if (this.checkPermission()) {
this.startProviderChooser();
} else {
ActivityCompat.requestPermissions(this, new String[]{"com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA"}, 1);
}
break;
case 1:
this.mWatchFace = (ComponentName)intent.getParcelableExtra("android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT_NAME");
if (this.checkPermission()) {
this.finish();
} else {
ActivityCompat.requestPermissions(this, new String[]{"com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA"}, 2);
}
break;
default:
throw new IllegalStateException("Unrecognised intent action.");
}
}
private void startProviderChooser() {
this.startActivityForResult(ProviderChooserIntent.createProviderChooserIntent(this.mWatchFace, this.mWfComplicationId, this.mTypes), 1);
}
}
public static Intent createProviderChooserIntent(ComponentName watchFace, int watchFaceComplicationId, int... supportedTypes) {
Intent intent = new Intent("com.google.android.clockwork.home.complications.ACTION_CHOOSE_PROVIDER");
intent.putExtra("android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT_NAME", watchFace);
intent.putExtra("android.support.wearable.complications.EXTRA_COMPLICATION_ID", watchFaceComplicationId);
intent.putExtra("android.support.wearable.complications.EXTRA_SUPPORTED_TYPES", supportedTypes);
return intent;
}
可以看到最终是使用
Intent intent = new Intent("com.google.android.clockwork.home.complications.ACTION_CHOOSE_PROVIDER");
这个activity的源码我们不一定能看到,但谷歌提供的示例代码
/vendor/google_clockwork_partners/packages/ClockworkHome//java/com/google/android/clockwork/home/complications/ProviderChooserActivity.java 也是对应这个action,所以可以跟进参考下,虽然不是最终代码,但与实际运行的代码基本一致。
跟踪发现,其用ComplicationController来插入db,db每个条目都记录了表盘名字,复杂数据的id,所能支持的类型。