1# Clock Plugins 2 3The clock appearing on the lock screen and always on display (AOD) can be customized via the 4ClockProviderPlugin plugin interface. The ClockPlugin interface has been removed. 5 6## Lock screen integration 7The lockscreen code has two main components, a [clock customization library](../customization), and 8the SystemUI [lockscreen host code](../src/com/android/keyguard). The customization library contains 9the default clock, and some support code for managing clocks and picking the correct one to render. 10It is used by both SystemUI for rendering and ThemePicker for selecting clocks. The SystemUI host is 11responsible for maintaining the view within the hierarchy and propagating events to the rendered 12clock controller. 13 14### Clock Library Code 15[ClockProvider and ClockController](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt) 16serve as the interface between the lockscreen (or other host application) and the clock that is 17being rendered. Implementing these interfaces is the primary integration point for rendering clocks 18in SystemUI. Many of the methods have an empty default implementation and are optional for 19implementations if the related event is not interesting to your use case. 20 21[DefaultClockProvider](../customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt) and 22[DefaultClockController](../customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt) 23implement these interfaces for the default lockscreen clock. They handle relevant events from the 24lockscreen to update and control the small and large clock view as appropriate. 25[AnimatableClockView](../customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt) 26is the view that DefaultClockController uses to render both the small and large clock. 27AnimatableClockView has moved location within the repo, but is largely unchanged from previous 28versions of android. 29 30The [ClockRegistry](../customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt) 31determines which clock should be shown, and handles creating them. It does this by maintaining a 32list of [ClockProviders](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt) and 33delegating work to them as appropriate. The DefaultClockProvider is compiled in so that it is 34guaranteed to be available, and additional ClockProviders are loaded at runtime via 35[PluginManager](../plugin_core/src/com/android/systemui/plugins/PluginManager.java). 36 37[ClockPlugin](../plugin/src/com/android/systemui/plugins/ClockPlugin.java) is deprecated and no 38longer used by keyguard to render clocks. The host code has been disabled but most of it is still 39present in the source tree, although it will likely be removed in a later patch. 40 41### Lockscreen Host 42[ClockEventController](../src/com/android/keyguard/ClockEventController.kt) propagates events from 43SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but 44otherwise attempts to do as little work as possible. It does maintain some state where necessary. 45 46[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is 47the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java), 48which serves as the view parent within SystemUI. Together they ensure the correct clock (either 49large or small) is shown, handle animation between clock sizes, and control some sizing/layout 50parameters for the clocks. 51 52### Creating a custom clock 53In order to create a custom clock, a partner must: 54 - Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case. 55 - Build this into a seperate plugin apk, and deploy that apk to the device. 56 - Alternatively, it could be compiled directly into the customization lib like DefaultClockProvider. 57 - PluginManager should automatically notify ClockRegistry of your plugin apk when it arrives on 58 device. ClockRegistry will print info logs when it successfully loads a plugin. 59 - Set the clock either in ThemePicker or through adb: 60 `adb shell settings put secure lock_screen_custom_clock_face '''{\"clockId\":\"ID\"}'''` 61 - SystemUI should immediately load and render the new clock if it is available. 62 63### Picker integration 64Picker logic for choosing between clocks is available to our partners as part of the ThemePicker. 65The clock picking UI will be enabled by default if there is more than 1 clock provided, otherwise 66it will be hidden from the UI. 67 68## System Health 69 70Clocks are high risk for battery consumption and screen burn-in because they modify the UI of AOD. 71 72To reduce battery consumption, it is recommended to target a maximum on-pixel-ratio (OPR) of 10%. 73Clocks that are composed of large blocks of color that cause the OPR to exceed 10% should be 74avoided, but this target will differ depending on the device hardware. 75 76To prevent screen burn-in, clocks should not be composed of large solid blocks of color, and the 77clock should be moved around the screen to distribute the on pixels across a large number of pixels. 78Software burn-in testing is a good starting point to assess the pixel shifting (clock movement) 79scheme and shape of the clock. SystemUI currently treats all clocks the same in this regard using 80[KeyguardClockPositionAlgorithm](../src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java) 81 82### Software Burn-In Test 83 84The goal is to look for bright spots in the luminosity average over a period of time. It is 85difficult to define a threshold where burn-in will occur. It is, therefore, recommended to compare 86against an element on AOD that is known not to cause problems. 87 88For clock face that contain color, it is recommended to use an all white version of the face. Since 89white has the highest luminosity, this version of the clock face represents the worst case scenario. 90 91To start, generate a sequence of screenshots for each minute over a 12 hr interval. 92 93``` 94serial = '84TY004MS' # serial number for the device 95count = 1 96t = datetime.datetime(2019, 1, 1) 97stop = t + datetime.timedelta(hours=12) 98if not os.path.exists(OUTPUT_FOLDER): 99 raise RuntimeError('output folder "%s" does not exist' % OUTPUT_FOLDER) 100while t <= stop: 101 os.system("adb -s %s shell 'date %s ; am broadcast -a android.intent.action.TIME_SET'" % (serial, t.strftime('%m%d%H%M%Y.%S'))) 102 os.system('adb -s %s shell screencap -p > %s/screencap_%06d.png' % (serial, OUTPUT_FOLDER, count)) 103 t += datetime.timedelta(minutes=1) 104 count += 1 105``` 106 107Average the luminosity of the screenshots. 108 109``` 110#!python 111import numpy 112import scipy.ndimage 113from imageio import imread, imwrite 114import matplotlib.pylab as plt 115import os 116import os.path 117 118def images(path): 119 return [os.path.join(path, name) for name in os.listdir(path) if name.endswith('.png')] 120 121def average(images): 122 AVG = None 123 for name in images: 124 IM = scipy.ndimage.imread(name, mode='L') 125 A = numpy.array(IM, dtype=numpy.double) 126 if AVG is None: 127 AVG = A 128 else: 129 AVG += A 130 AVG /= len(images) 131 return numpy.array(AVG, dtype=numpy.uint8) 132 133def main(path): 134 ims = images(path) 135 if len(ims) == 0: 136 raise ValueError("folder '%s' doesn't contain any png files" % path) 137 AVG = average(ims) 138 imwrite('average.png', AVG) 139 plt.imshow(AVG) 140 plt.show() 141 142if __name__=='__main__': 143 import sys 144 main(sys.argv[1]) 145``` 146 147Look for bright spots in the luminosity average. If bright spots are found, action should be taken 148to change the shape of the clock face or increase the amount of pixel shifting. 149