1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android-base/stringprintf.h>
18 #include <batteryservice/BatteryService.h>
19 #include <cutils/klog.h>
20 
21 #include "healthd_draw.h"
22 
23 #if !defined(__ANDROID_VNDK__)
24 #include "charger.sysprop.h"
25 #endif
26 
27 #define LOGE(x...) KLOG_ERROR("charger", x);
28 #define LOGW(x...) KLOG_WARNING("charger", x);
29 #define LOGV(x...) KLOG_DEBUG("charger", x);
30 
get_split_screen()31 static bool get_split_screen() {
32 #if !defined(__ANDROID_VNDK__)
33     return android::sysprop::ChargerProperties::draw_split_screen().value_or(false);
34 #else
35     return false;
36 #endif
37 }
38 
get_split_offset()39 static int get_split_offset() {
40 #if !defined(__ANDROID_VNDK__)
41     int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0);
42 #else
43     int64_t value = 0;
44 #endif
45     if (value < static_cast<int64_t>(std::numeric_limits<int>::min())) {
46         LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
47              std::numeric_limits<int>::min());
48         value = std::numeric_limits<int>::min();
49     }
50     if (value > static_cast<int64_t>(std::numeric_limits<int>::max())) {
51         LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
52              std::numeric_limits<int>::max());
53         value = std::numeric_limits<int>::max();
54     }
55     return static_cast<int>(value);
56 }
57 
HealthdDraw(animation * anim)58 HealthdDraw::HealthdDraw(animation* anim)
59     : kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {
60     graphics_available = true;
61     sys_font = gr_sys_font();
62     if (sys_font == nullptr) {
63         LOGW("No system font, screen fallback text not available\n");
64     } else {
65         gr_font_size(sys_font, &char_width_, &char_height_);
66     }
67 
68     screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1);
69     screen_height_ = gr_fb_height();
70 
71     int res;
72     if (!anim->text_clock.font_file.empty() &&
73         (res = gr_init_font(anim->text_clock.font_file.c_str(), &anim->text_clock.font)) < 0) {
74         LOGE("Could not load time font (%d)\n", res);
75     }
76     if (!anim->text_percent.font_file.empty() &&
77         (res = gr_init_font(anim->text_percent.font_file.c_str(), &anim->text_percent.font)) < 0) {
78         LOGE("Could not load percent font (%d)\n", res);
79     }
80 }
81 
~HealthdDraw()82 HealthdDraw::~HealthdDraw() {}
83 
redraw_screen(const animation * batt_anim,GRSurface * surf_unknown)84 void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) {
85     if (!graphics_available) return;
86     clear_screen();
87 
88     /* try to display *something* */
89     if (batt_anim->cur_status == BATTERY_STATUS_UNKNOWN || batt_anim->cur_level < 0 ||
90         batt_anim->num_frames == 0)
91         draw_unknown(surf_unknown);
92     else
93         draw_battery(batt_anim);
94     gr_flip();
95 }
96 
blank_screen(bool blank,int drm)97 void HealthdDraw::blank_screen(bool blank, int drm) {
98     if (!graphics_available) return;
99     gr_fb_blank(blank, drm);
100 }
101 
102 // support screen rotation for foldable phone
rotate_screen(int drm)103 void HealthdDraw::rotate_screen(int drm) {
104     if (!graphics_available) return;
105     if (drm == 0)
106         gr_rotate(GRRotation::RIGHT /* landscape mode */);
107     else
108         gr_rotate(GRRotation::NONE /* Portrait mode */);
109 }
110 
111 // detect dual display
has_multiple_connectors()112 bool HealthdDraw::has_multiple_connectors() {
113     return graphics_available && gr_has_multiple_connectors();
114 }
115 
clear_screen(void)116 void HealthdDraw::clear_screen(void) {
117     if (!graphics_available) return;
118     gr_color(0, 0, 0, 255);
119     gr_clear();
120 }
121 
draw_surface_centered(GRSurface * surface)122 int HealthdDraw::draw_surface_centered(GRSurface* surface) {
123     if (!graphics_available) return 0;
124 
125     int w = gr_get_width(surface);
126     int h = gr_get_height(surface);
127     int x = (screen_width_ - w) / 2 + kSplitOffset;
128     int y = (screen_height_ - h) / 2;
129 
130     LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y);
131     gr_blit(surface, 0, 0, w, h, x, y);
132     if (kSplitScreen) {
133         x += screen_width_ - 2 * kSplitOffset;
134         LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y);
135         gr_blit(surface, 0, 0, w, h, x, y);
136     }
137 
138     return y + h;
139 }
140 
draw_text(const GRFont * font,int x,int y,const char * str)141 int HealthdDraw::draw_text(const GRFont* font, int x, int y, const char* str) {
142     if (!graphics_available) return 0;
143     int str_len_px = gr_measure(font, str);
144 
145     if (x < 0) x = (screen_width_ - str_len_px) / 2;
146     if (y < 0) y = (screen_height_ - char_height_) / 2;
147     gr_text(font, x + kSplitOffset, y, str, false /* bold */);
148     if (kSplitScreen) gr_text(font, x - kSplitOffset + screen_width_, y, str, false /* bold */);
149 
150     return y + char_height_;
151 }
152 
determine_xy(const animation::text_field & field,const int length,int * x,int * y)153 void HealthdDraw::determine_xy(const animation::text_field& field,
154                                const int length, int* x, int* y) {
155   *x = field.pos_x;
156   screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1);
157   screen_height_ = gr_fb_height();
158 
159   int str_len_px = length * field.font->char_width;
160   if (field.pos_x == CENTER_VAL) {
161     *x = (screen_width_ - str_len_px) / 2;
162   } else if (field.pos_x >= 0) {
163     *x = field.pos_x;
164   } else {  // position from max edge
165     *x = screen_width_ + field.pos_x - str_len_px - kSplitOffset;
166   }
167 
168   *y = field.pos_y;
169 
170   if (field.pos_y == CENTER_VAL) {
171     *y = (screen_height_ - field.font->char_height) / 2;
172   } else if (field.pos_y >= 0) {
173     *y = field.pos_y;
174   } else {  // position from max edge
175     *y = screen_height_ + field.pos_y - field.font->char_height;
176   }
177 }
178 
draw_clock(const animation * anim)179 void HealthdDraw::draw_clock(const animation* anim) {
180     static constexpr char CLOCK_FORMAT[] = "%H:%M";
181     static constexpr int CLOCK_LENGTH = 6;
182 
183     const animation::text_field& field = anim->text_clock;
184 
185     if (!graphics_available || field.font == nullptr || field.font->char_width == 0 ||
186         field.font->char_height == 0)
187         return;
188 
189     time_t rawtime;
190     time(&rawtime);
191     tm* time_info = localtime(&rawtime);
192 
193     char clock_str[CLOCK_LENGTH];
194     size_t length = strftime(clock_str, CLOCK_LENGTH, CLOCK_FORMAT, time_info);
195     if (length != CLOCK_LENGTH - 1) {
196         LOGE("Could not format time\n");
197         return;
198     }
199 
200     int x, y;
201     determine_xy(field, length, &x, &y);
202 
203     LOGV("drawing clock %s %d %d\n", clock_str, x, y);
204     gr_color(field.color_r, field.color_g, field.color_b, field.color_a);
205     draw_text(field.font, x, y, clock_str);
206 }
207 
draw_percent(const animation * anim)208 void HealthdDraw::draw_percent(const animation* anim) {
209     if (!graphics_available) return;
210     int cur_level = anim->cur_level;
211     if (anim->cur_status == BATTERY_STATUS_FULL) {
212         cur_level = 100;
213     }
214 
215     if (cur_level < 0) return;
216 
217     const animation::text_field& field = anim->text_percent;
218     if (field.font == nullptr || field.font->char_width == 0 || field.font->char_height == 0) {
219         return;
220     }
221 
222     std::string str = base::StringPrintf("%d%%", cur_level);
223 
224     int x, y;
225     determine_xy(field, str.size(), &x, &y);
226 
227     LOGV("drawing percent %s %d %d\n", str.c_str(), x, y);
228     gr_color(field.color_r, field.color_g, field.color_b, field.color_a);
229     draw_text(field.font, x, y, str.c_str());
230 }
231 
draw_battery(const animation * anim)232 void HealthdDraw::draw_battery(const animation* anim) {
233     if (!graphics_available) return;
234     const animation::frame& frame = anim->frames[anim->cur_frame];
235 
236     if (anim->num_frames != 0) {
237         draw_surface_centered(frame.surface);
238         LOGV("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame, frame.min_level,
239              frame.disp_time);
240     }
241     draw_clock(anim);
242     draw_percent(anim);
243 }
244 
draw_unknown(GRSurface * surf_unknown)245 void HealthdDraw::draw_unknown(GRSurface* surf_unknown) {
246   int y;
247   if (surf_unknown) {
248       draw_surface_centered(surf_unknown);
249   } else if (sys_font) {
250       gr_color(0xa4, 0xc6, 0x39, 255);
251       y = draw_text(sys_font, -1, -1, "Charging!");
252       draw_text(sys_font, -1, y + 25, "?\?/100");
253   } else {
254       LOGW("Charging, level unknown\n");
255   }
256 }
257 
Create(animation * anim)258 std::unique_ptr<HealthdDraw> HealthdDraw::Create(animation *anim) {
259     if (gr_init() < 0) {
260         LOGE("gr_init failed\n");
261         return nullptr;
262     }
263     return std::unique_ptr<HealthdDraw>(new HealthdDraw(anim));
264 }
265