WearOS复杂数据的刷新

表盘可以通过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中指定默认刷新时间。

 <meta-data
        android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
        android:value="300" />

表盘处于活动状态时,会按照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<TimeFormatText> 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分钟的整数倍。

a.时区设置后数据不更新

在开发中,我自己实现了一个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来选择复杂数据。
  <provider
 859             android:name="com.google.android.clockwork.home.application.LongLivedProcessProvider"
 860             android:authorities="com.google.android.wearable.app.process.longlived"
 861             android:exported="false"
 862             android:initOrder="100" />
 863         <activity
 864             android:name="com.google.android.clockwork.home.complications.ProviderChooserActivity"
 865             android:exported="true"
 866             android:taskAffinity=""
 867             android:theme="@style/ComplicationSettings" >
 868             <intent-filter>
 869                 <action android:name="com.google.android.clockwork.home.complications.ACTION_CHOOSE_PROVIDER" />
 870 
 871                 <category android:name="android.intent.category.DEFAULT" />
 872             </intent-filter>
 873         </activity>

与复杂数据刷新相关的,重点要看的是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只刷了系统复杂数据,没有去刷别的复杂数据,这就需要用户自己监听时区变化再刷新复杂数据。

  <receiver android:name=".complications.TimeZoneReceiver">
            <intent-filter>
                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
            </intent-filter>
        </receiver>
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);
    }
}

b.Offload绘制小时分钟复杂数据显示异常。

 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帧。

C. WearOS表盘复杂数据选中存储

复杂数据配置信息记录在/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,所能支持的类型。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值