blob: bf487c4aec36ba5c02ea9883650aa3907daaaa16 [file] [log] [blame]
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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#define LOG_TAG "Region"
18
Mark Salyzyn92dc3fc2014-03-12 13:12:44 -070019#include <inttypes.h>
Mathias Agopian72b0ffe2009-07-06 18:07:26 -070020#include <limits.h>
21
Yiwei Zhang5434a782018-12-05 18:06:32 -080022#include <android-base/stringprintf.h>
23
Mathias Agopian20f68782009-05-11 00:03:41 -070024#include <utils/Log.h>
Mathias Agopian20f68782009-05-11 00:03:41 -070025
Marissa Wall15375f92019-12-03 15:10:46 -080026#include <ui/Point.h>
Mathias Agopian20f68782009-05-11 00:03:41 -070027#include <ui/Rect.h>
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080028#include <ui/Region.h>
Marissa Wall15375f92019-12-03 15:10:46 -080029#include <ui/RegionHelper.h>
Mathias Agopian20f68782009-05-11 00:03:41 -070030
31// ----------------------------------------------------------------------------
Chong Zhang639a1e12019-04-22 14:01:20 -070032
33// ### VALIDATE_REGIONS ###
34// To enable VALIDATE_REGIONS traces, use the "libui-validate-regions-defaults"
35// in Android.bp. Do not #define VALIDATE_REGIONS here as it requires extra libs.
36
Mathias Agopian20f68782009-05-11 00:03:41 -070037#define VALIDATE_WITH_CORECG (false)
38// ----------------------------------------------------------------------------
39
Chong Zhang639a1e12019-04-22 14:01:20 -070040#if defined(VALIDATE_REGIONS)
41#include <utils/CallStack.h>
42#endif
43
Mathias Agopian20f68782009-05-11 00:03:41 -070044#if VALIDATE_WITH_CORECG
45#include <core/SkRegion.h>
46#endif
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080047
48namespace android {
Mathias Agopian20f68782009-05-11 00:03:41 -070049// ----------------------------------------------------------------------------
50
Yiwei Zhang5434a782018-12-05 18:06:32 -080051using base::StringAppendF;
52
Mathias Agopian20f68782009-05-11 00:03:41 -070053enum {
54 op_nand = region_operator<Rect>::op_nand,
55 op_and = region_operator<Rect>::op_and,
56 op_or = region_operator<Rect>::op_or,
57 op_xor = region_operator<Rect>::op_xor
58};
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080059
Chris Craik3e010f32013-02-25 19:12:47 -080060enum {
61 direction_LTR,
62 direction_RTL
63};
64
Dan Stoza5065a552015-03-17 16:23:42 -070065const Region Region::INVALID_REGION(Rect::INVALID_RECT);
66
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080067// ----------------------------------------------------------------------------
68
Mathias Agopian3ab68552012-08-31 14:31:40 -070069Region::Region() {
70 mStorage.add(Rect(0,0));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080071}
72
73Region::Region(const Region& rhs)
Mathias Agopian3ab68552012-08-31 14:31:40 -070074 : mStorage(rhs.mStorage)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080075{
Chong Zhang639a1e12019-04-22 14:01:20 -070076#if defined(VALIDATE_REGIONS)
Mathias Agopiand0b55c02011-03-16 23:18:07 -070077 validate(rhs, "rhs copy-ctor");
78#endif
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080079}
80
Mathias Agopian3ab68552012-08-31 14:31:40 -070081Region::Region(const Rect& rhs) {
82 mStorage.add(rhs);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080083}
84
85Region::~Region()
86{
87}
88
Chris Craik3e010f32013-02-25 19:12:47 -080089/**
90 * Copy rects from the src vector into the dst vector, resolving vertical T-Junctions along the way
91 *
92 * First pass through, divideSpanRTL will be set because the 'previous span' (indexing into the dst
93 * vector) will be reversed. Each rectangle in the original list, starting from the bottom, will be
94 * compared with the span directly below, and subdivided as needed to resolve T-junctions.
95 *
96 * The resulting temporary vector will be a completely reversed copy of the original, without any
97 * bottom-up T-junctions.
98 *
99 * Second pass through, divideSpanRTL will be false since the previous span will index into the
100 * final, correctly ordered region buffer. Each rectangle will be compared with the span directly
101 * above it, and subdivided to resolve any remaining T-junctions.
102 */
103static void reverseRectsResolvingJunctions(const Rect* begin, const Rect* end,
104 Vector<Rect>& dst, int spanDirection) {
105 dst.clear();
106
107 const Rect* current = end - 1;
108 int lastTop = current->top;
109
110 // add first span immediately
111 do {
112 dst.add(*current);
113 current--;
114 } while (current->top == lastTop && current >= begin);
115
Dan Stozad3182402014-11-17 12:03:59 -0800116 int beginLastSpan = -1;
117 int endLastSpan = -1;
Chris Craik3e010f32013-02-25 19:12:47 -0800118 int top = -1;
119 int bottom = -1;
120
121 // for all other spans, split if a t-junction exists in the span directly above
122 while (current >= begin) {
123 if (current->top != (current + 1)->top) {
124 // new span
125 if ((spanDirection == direction_RTL && current->bottom != (current + 1)->top) ||
126 (spanDirection == direction_LTR && current->top != (current + 1)->bottom)) {
127 // previous span not directly adjacent, don't check for T junctions
128 beginLastSpan = INT_MAX;
129 } else {
130 beginLastSpan = endLastSpan + 1;
131 }
Dan Stozad3182402014-11-17 12:03:59 -0800132 endLastSpan = static_cast<int>(dst.size()) - 1;
Chris Craik3e010f32013-02-25 19:12:47 -0800133
134 top = current->top;
135 bottom = current->bottom;
136 }
137 int left = current->left;
138 int right = current->right;
139
Dan Stozad3182402014-11-17 12:03:59 -0800140 for (int prevIndex = beginLastSpan; prevIndex <= endLastSpan; prevIndex++) {
141 // prevIndex can't be -1 here because if endLastSpan is set to a
142 // value greater than -1 (allowing the loop to execute),
143 // beginLastSpan (and therefore prevIndex) will also be increased
ywenaef04452015-03-26 19:51:12 +0800144 const Rect prev = dst[static_cast<size_t>(prevIndex)];
Chris Craik3e010f32013-02-25 19:12:47 -0800145 if (spanDirection == direction_RTL) {
146 // iterating over previous span RTL, quit if it's too far left
ywenaef04452015-03-26 19:51:12 +0800147 if (prev.right <= left) break;
Chris Craik3e010f32013-02-25 19:12:47 -0800148
ywenaef04452015-03-26 19:51:12 +0800149 if (prev.right > left && prev.right < right) {
150 dst.add(Rect(prev.right, top, right, bottom));
151 right = prev.right;
Chris Craik3e010f32013-02-25 19:12:47 -0800152 }
153
ywenaef04452015-03-26 19:51:12 +0800154 if (prev.left > left && prev.left < right) {
155 dst.add(Rect(prev.left, top, right, bottom));
156 right = prev.left;
Chris Craik3e010f32013-02-25 19:12:47 -0800157 }
158
159 // if an entry in the previous span is too far right, nothing further left in the
160 // current span will need it
ywenaef04452015-03-26 19:51:12 +0800161 if (prev.left >= right) {
Chris Craik3e010f32013-02-25 19:12:47 -0800162 beginLastSpan = prevIndex;
163 }
164 } else {
165 // iterating over previous span LTR, quit if it's too far right
ywenaef04452015-03-26 19:51:12 +0800166 if (prev.left >= right) break;
Chris Craik3e010f32013-02-25 19:12:47 -0800167
ywenaef04452015-03-26 19:51:12 +0800168 if (prev.left > left && prev.left < right) {
169 dst.add(Rect(left, top, prev.left, bottom));
170 left = prev.left;
Chris Craik3e010f32013-02-25 19:12:47 -0800171 }
172
ywenaef04452015-03-26 19:51:12 +0800173 if (prev.right > left && prev.right < right) {
174 dst.add(Rect(left, top, prev.right, bottom));
175 left = prev.right;
Chris Craik3e010f32013-02-25 19:12:47 -0800176 }
177 // if an entry in the previous span is too far left, nothing further right in the
178 // current span will need it
ywenaef04452015-03-26 19:51:12 +0800179 if (prev.right <= left) {
Chris Craik3e010f32013-02-25 19:12:47 -0800180 beginLastSpan = prevIndex;
181 }
182 }
183 }
184
185 if (left < right) {
186 dst.add(Rect(left, top, right, bottom));
187 }
188
189 current--;
190 }
191}
192
193/**
194 * Creates a new region with the same data as the argument, but divides rectangles as necessary to
195 * remove T-Junctions
196 *
197 * Note: the output will not necessarily be a very efficient representation of the region, since it
198 * may be that a triangle-based approach would generate significantly simpler geometry
199 */
200Region Region::createTJunctionFreeRegion(const Region& r) {
201 if (r.isEmpty()) return r;
202 if (r.isRect()) return r;
203
204 Vector<Rect> reversed;
205 reverseRectsResolvingJunctions(r.begin(), r.end(), reversed, direction_RTL);
206
207 Region outputRegion;
208 reverseRectsResolvingJunctions(reversed.begin(), reversed.end(),
209 outputRegion.mStorage, direction_LTR);
210 outputRegion.mStorage.add(r.getBounds()); // to make region valid, mStorage must end with bounds
211
Chong Zhang639a1e12019-04-22 14:01:20 -0700212#if defined(VALIDATE_REGIONS)
Chris Craik3e010f32013-02-25 19:12:47 -0800213 validate(outputRegion, "T-Junction free region");
214#endif
215
216 return outputRegion;
217}
218
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800219Region& Region::operator = (const Region& rhs)
220{
Chong Zhang639a1e12019-04-22 14:01:20 -0700221#if defined(VALIDATE_REGIONS)
Mathias Agopiand0b55c02011-03-16 23:18:07 -0700222 validate(*this, "this->operator=");
223 validate(rhs, "rhs.operator=");
Mathias Agopian20f68782009-05-11 00:03:41 -0700224#endif
Mathias Agopian20f68782009-05-11 00:03:41 -0700225 mStorage = rhs.mStorage;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800226 return *this;
227}
228
Mathias Agopian9f961452009-06-29 18:46:37 -0700229Region& Region::makeBoundsSelf()
230{
Mathias Agopian3ab68552012-08-31 14:31:40 -0700231 if (mStorage.size() >= 2) {
232 const Rect bounds(getBounds());
233 mStorage.clear();
234 mStorage.add(bounds);
235 }
Mathias Agopian9f961452009-06-29 18:46:37 -0700236 return *this;
237}
238
Michael Wright1c284a92014-02-10 13:00:14 -0800239bool Region::contains(const Point& point) const {
240 return contains(point.x, point.y);
241}
242
243bool Region::contains(int x, int y) const {
244 const_iterator cur = begin();
245 const_iterator const tail = end();
246 while (cur != tail) {
247 if (y >= cur->top && y < cur->bottom && x >= cur->left && x < cur->right) {
248 return true;
249 }
250 cur++;
251 }
252 return false;
253}
254
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800255void Region::clear()
256{
Mathias Agopian20f68782009-05-11 00:03:41 -0700257 mStorage.clear();
Mathias Agopian3ab68552012-08-31 14:31:40 -0700258 mStorage.add(Rect(0,0));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800259}
260
261void Region::set(const Rect& r)
262{
Mathias Agopian20f68782009-05-11 00:03:41 -0700263 mStorage.clear();
Mathias Agopian3ab68552012-08-31 14:31:40 -0700264 mStorage.add(r);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800265}
266
Dan Stozad3182402014-11-17 12:03:59 -0800267void Region::set(int32_t w, int32_t h)
Mathias Agopian0926f502009-05-04 14:17:04 -0700268{
Mathias Agopian20f68782009-05-11 00:03:41 -0700269 mStorage.clear();
Dan Stozad3182402014-11-17 12:03:59 -0800270 mStorage.add(Rect(w, h));
Mathias Agopian0926f502009-05-04 14:17:04 -0700271}
272
Bernhard Rosenkraenzerfe4966d2014-12-22 21:15:08 +0100273void Region::set(uint32_t w, uint32_t h)
274{
275 mStorage.clear();
276 mStorage.add(Rect(w, h));
277}
278
Mathias Agopian2ca79392013-04-02 18:30:32 -0700279bool Region::isTriviallyEqual(const Region& region) const {
280 return begin() == region.begin();
281}
282
Lloyd Piqueea629282019-12-03 15:57:10 -0800283bool Region::hasSameRects(const Region& other) const {
284 size_t thisRectCount = 0;
285 android::Rect const* thisRects = getArray(&thisRectCount);
286 size_t otherRectCount = 0;
287 android::Rect const* otherRects = other.getArray(&otherRectCount);
288
289 if (thisRectCount != otherRectCount) return false;
290
291 for (size_t i = 0; i < thisRectCount; i++) {
292 if (thisRects[i] != otherRects[i]) return false;
293 }
294 return true;
295}
296
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800297// ----------------------------------------------------------------------------
298
Mathias Agopian20f68782009-05-11 00:03:41 -0700299void Region::addRectUnchecked(int l, int t, int r, int b)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800300{
Mathias Agopian3ab68552012-08-31 14:31:40 -0700301 Rect rect(l,t,r,b);
302 size_t where = mStorage.size() - 1;
303 mStorage.insertAt(rect, where, 1);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800304}
305
Mathias Agopian20f68782009-05-11 00:03:41 -0700306// ----------------------------------------------------------------------------
307
308Region& Region::orSelf(const Rect& r) {
309 return operationSelf(r, op_or);
310}
Romain Guyb8a2e982012-02-07 17:04:34 -0800311Region& Region::xorSelf(const Rect& r) {
312 return operationSelf(r, op_xor);
313}
Mathias Agopian20f68782009-05-11 00:03:41 -0700314Region& Region::andSelf(const Rect& r) {
315 return operationSelf(r, op_and);
316}
317Region& Region::subtractSelf(const Rect& r) {
318 return operationSelf(r, op_nand);
319}
Colin Cross8f279962016-09-26 13:08:16 -0700320Region& Region::operationSelf(const Rect& r, uint32_t op) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700321 Region lhs(*this);
322 boolean_operation(op, *this, lhs, r);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800323 return *this;
324}
325
326// ----------------------------------------------------------------------------
327
328Region& Region::orSelf(const Region& rhs) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700329 return operationSelf(rhs, op_or);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800330}
Romain Guyb8a2e982012-02-07 17:04:34 -0800331Region& Region::xorSelf(const Region& rhs) {
332 return operationSelf(rhs, op_xor);
333}
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800334Region& Region::andSelf(const Region& rhs) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700335 return operationSelf(rhs, op_and);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800336}
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800337Region& Region::subtractSelf(const Region& rhs) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700338 return operationSelf(rhs, op_nand);
339}
Colin Cross8f279962016-09-26 13:08:16 -0700340Region& Region::operationSelf(const Region& rhs, uint32_t op) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700341 Region lhs(*this);
342 boolean_operation(op, *this, lhs, rhs);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800343 return *this;
344}
345
346Region& Region::translateSelf(int x, int y) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700347 if (x|y) translate(*this, x, y);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800348 return *this;
349}
350
Riddle Hsu39d4aa52018-11-30 20:46:53 +0800351Region& Region::scaleSelf(float sx, float sy) {
Robert Carre07e1032018-11-26 12:55:53 -0800352 size_t count = mStorage.size();
353 Rect* rects = mStorage.editArray();
354 while (count) {
Nick Desaulniersea6c7132019-10-15 19:14:39 -0700355 rects->left = static_cast<int32_t>(static_cast<float>(rects->left) * sx + 0.5f);
356 rects->right = static_cast<int32_t>(static_cast<float>(rects->right) * sx + 0.5f);
357 rects->top = static_cast<int32_t>(static_cast<float>(rects->top) * sy + 0.5f);
358 rects->bottom = static_cast<int32_t>(static_cast<float>(rects->bottom) * sy + 0.5f);
Robert Carre07e1032018-11-26 12:55:53 -0800359 rects++;
360 count--;
361 }
362 return *this;
363}
364
Mathias Agopian20f68782009-05-11 00:03:41 -0700365// ----------------------------------------------------------------------------
366
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700367const Region Region::merge(const Rect& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700368 return operation(rhs, op_or);
369}
Romain Guyb8a2e982012-02-07 17:04:34 -0800370const Region Region::mergeExclusive(const Rect& rhs) const {
371 return operation(rhs, op_xor);
372}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700373const Region Region::intersect(const Rect& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700374 return operation(rhs, op_and);
375}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700376const Region Region::subtract(const Rect& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700377 return operation(rhs, op_nand);
378}
Colin Cross8f279962016-09-26 13:08:16 -0700379const Region Region::operation(const Rect& rhs, uint32_t op) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700380 Region result;
381 boolean_operation(op, result, *this, rhs);
382 return result;
383}
384
385// ----------------------------------------------------------------------------
386
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700387const Region Region::merge(const Region& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700388 return operation(rhs, op_or);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800389}
Romain Guyb8a2e982012-02-07 17:04:34 -0800390const Region Region::mergeExclusive(const Region& rhs) const {
391 return operation(rhs, op_xor);
392}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700393const Region Region::intersect(const Region& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700394 return operation(rhs, op_and);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800395}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700396const Region Region::subtract(const Region& rhs) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700397 return operation(rhs, op_nand);
398}
Colin Cross8f279962016-09-26 13:08:16 -0700399const Region Region::operation(const Region& rhs, uint32_t op) const {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800400 Region result;
Mathias Agopian20f68782009-05-11 00:03:41 -0700401 boolean_operation(op, result, *this, rhs);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800402 return result;
403}
404
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700405const Region Region::translate(int x, int y) const {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800406 Region result;
Mathias Agopian20f68782009-05-11 00:03:41 -0700407 translate(result, *this, x, y);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800408 return result;
409}
410
411// ----------------------------------------------------------------------------
412
413Region& Region::orSelf(const Region& rhs, int dx, int dy) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700414 return operationSelf(rhs, dx, dy, op_or);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800415}
Romain Guyb8a2e982012-02-07 17:04:34 -0800416Region& Region::xorSelf(const Region& rhs, int dx, int dy) {
417 return operationSelf(rhs, dx, dy, op_xor);
418}
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800419Region& Region::andSelf(const Region& rhs, int dx, int dy) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700420 return operationSelf(rhs, dx, dy, op_and);
421}
422Region& Region::subtractSelf(const Region& rhs, int dx, int dy) {
423 return operationSelf(rhs, dx, dy, op_nand);
424}
Colin Cross8f279962016-09-26 13:08:16 -0700425Region& Region::operationSelf(const Region& rhs, int dx, int dy, uint32_t op) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700426 Region lhs(*this);
427 boolean_operation(op, *this, lhs, rhs, dx, dy);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800428 return *this;
429}
430
Mathias Agopian20f68782009-05-11 00:03:41 -0700431// ----------------------------------------------------------------------------
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800432
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700433const Region Region::merge(const Region& rhs, int dx, int dy) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700434 return operation(rhs, dx, dy, op_or);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800435}
Romain Guyb8a2e982012-02-07 17:04:34 -0800436const Region Region::mergeExclusive(const Region& rhs, int dx, int dy) const {
437 return operation(rhs, dx, dy, op_xor);
438}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700439const Region Region::intersect(const Region& rhs, int dx, int dy) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700440 return operation(rhs, dx, dy, op_and);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800441}
Mathias Agopianbed9dd12009-05-27 17:01:58 -0700442const Region Region::subtract(const Region& rhs, int dx, int dy) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700443 return operation(rhs, dx, dy, op_nand);
444}
Colin Cross8f279962016-09-26 13:08:16 -0700445const Region Region::operation(const Region& rhs, int dx, int dy, uint32_t op) const {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800446 Region result;
Mathias Agopian20f68782009-05-11 00:03:41 -0700447 boolean_operation(op, result, *this, rhs, dx, dy);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800448 return result;
449}
450
451// ----------------------------------------------------------------------------
452
Mathias Agopian20f68782009-05-11 00:03:41 -0700453// This is our region rasterizer, which merges rects and spans together
454// to obtain an optimal region.
Dan Stozad3182402014-11-17 12:03:59 -0800455class Region::rasterizer : public region_operator<Rect>::region_rasterizer
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800456{
Mathias Agopian3ab68552012-08-31 14:31:40 -0700457 Rect bounds;
Mathias Agopian20f68782009-05-11 00:03:41 -0700458 Vector<Rect>& storage;
459 Rect* head;
460 Rect* tail;
461 Vector<Rect> span;
462 Rect* cur;
463public:
Chih-Hung Hsiehe2347b72016-04-25 15:41:05 -0700464 explicit rasterizer(Region& reg)
Mathias Agopian3ab68552012-08-31 14:31:40 -0700465 : bounds(INT_MAX, 0, INT_MIN, 0), storage(reg.mStorage), head(), tail(), cur() {
Mathias Agopian20f68782009-05-11 00:03:41 -0700466 storage.clear();
467 }
468
Dan Stozad3182402014-11-17 12:03:59 -0800469 virtual ~rasterizer();
470
471 virtual void operator()(const Rect& rect);
472
Mathias Agopian20f68782009-05-11 00:03:41 -0700473private:
Dan Stozad3182402014-11-17 12:03:59 -0800474 template<typename T>
Mathias Agopian20f68782009-05-11 00:03:41 -0700475 static inline T min(T rhs, T lhs) { return rhs < lhs ? rhs : lhs; }
Dan Stozad3182402014-11-17 12:03:59 -0800476 template<typename T>
Mathias Agopian20f68782009-05-11 00:03:41 -0700477 static inline T max(T rhs, T lhs) { return rhs > lhs ? rhs : lhs; }
Dan Stozad3182402014-11-17 12:03:59 -0800478
479 void flushSpan();
Mathias Agopian20f68782009-05-11 00:03:41 -0700480};
481
Dan Stozad3182402014-11-17 12:03:59 -0800482Region::rasterizer::~rasterizer()
483{
484 if (span.size()) {
485 flushSpan();
486 }
487 if (storage.size()) {
488 bounds.top = storage.itemAt(0).top;
489 bounds.bottom = storage.top().bottom;
490 if (storage.size() == 1) {
491 storage.clear();
492 }
493 } else {
494 bounds.left = 0;
495 bounds.right = 0;
496 }
497 storage.add(bounds);
498}
499
500void Region::rasterizer::operator()(const Rect& rect)
501{
502 //ALOGD(">>> %3d, %3d, %3d, %3d",
503 // rect.left, rect.top, rect.right, rect.bottom);
504 if (span.size()) {
505 if (cur->top != rect.top) {
506 flushSpan();
507 } else if (cur->right == rect.left) {
508 cur->right = rect.right;
509 return;
510 }
511 }
512 span.add(rect);
513 cur = span.editArray() + (span.size() - 1);
514}
515
516void Region::rasterizer::flushSpan()
517{
518 bool merge = false;
519 if (tail-head == ssize_t(span.size())) {
520 Rect const* p = span.editArray();
521 Rect const* q = head;
522 if (p->top == q->bottom) {
523 merge = true;
524 while (q != tail) {
525 if ((p->left != q->left) || (p->right != q->right)) {
526 merge = false;
527 break;
528 }
Stephen Hines9c22c3c2016-03-31 22:02:38 -0700529 p++;
530 q++;
Dan Stozad3182402014-11-17 12:03:59 -0800531 }
532 }
533 }
534 if (merge) {
535 const int bottom = span[0].bottom;
536 Rect* r = head;
537 while (r != tail) {
538 r->bottom = bottom;
539 r++;
540 }
541 } else {
542 bounds.left = min(span.itemAt(0).left, bounds.left);
543 bounds.right = max(span.top().right, bounds.right);
544 storage.appendVector(span);
545 tail = storage.editArray() + storage.size();
546 head = tail - span.size();
547 }
548 span.clear();
549}
550
Mathias Agopian068d47f2012-09-11 18:56:23 -0700551bool Region::validate(const Region& reg, const char* name, bool silent)
Mathias Agopian20f68782009-05-11 00:03:41 -0700552{
Chia-I Wub420b582018-02-07 11:53:41 -0800553 if (reg.mStorage.isEmpty()) {
554 ALOGE_IF(!silent, "%s: mStorage is empty, which is never valid", name);
555 // return immediately as the code below assumes mStorage is non-empty
556 return false;
557 }
558
Mathias Agopian20f68782009-05-11 00:03:41 -0700559 bool result = true;
560 const_iterator cur = reg.begin();
561 const_iterator const tail = reg.end();
Mathias Agopian068d47f2012-09-11 18:56:23 -0700562 const_iterator prev = cur;
Mathias Agopian20f68782009-05-11 00:03:41 -0700563 Rect b(*prev);
564 while (cur != tail) {
Mathias Agopian068d47f2012-09-11 18:56:23 -0700565 if (cur->isValid() == false) {
Dan Stoza5065a552015-03-17 16:23:42 -0700566 // We allow this particular flavor of invalid Rect, since it is used
567 // as a signal value in various parts of the system
568 if (*cur != Rect::INVALID_RECT) {
569 ALOGE_IF(!silent, "%s: region contains an invalid Rect", name);
570 result = false;
571 }
Mathias Agopian068d47f2012-09-11 18:56:23 -0700572 }
573 if (cur->right > region_operator<Rect>::max_value) {
574 ALOGE_IF(!silent, "%s: rect->right > max_value", name);
575 result = false;
576 }
577 if (cur->bottom > region_operator<Rect>::max_value) {
578 ALOGE_IF(!silent, "%s: rect->right > max_value", name);
579 result = false;
580 }
581 if (prev != cur) {
582 b.left = b.left < cur->left ? b.left : cur->left;
583 b.top = b.top < cur->top ? b.top : cur->top;
584 b.right = b.right > cur->right ? b.right : cur->right;
585 b.bottom = b.bottom > cur->bottom ? b.bottom : cur->bottom;
586 if ((*prev < *cur) == false) {
587 ALOGE_IF(!silent, "%s: region's Rects not sorted", name);
Mathias Agopian20f68782009-05-11 00:03:41 -0700588 result = false;
Mathias Agopian068d47f2012-09-11 18:56:23 -0700589 }
590 if (cur->top == prev->top) {
591 if (cur->bottom != prev->bottom) {
592 ALOGE_IF(!silent, "%s: invalid span %p", name, cur);
593 result = false;
594 } else if (cur->left < prev->right) {
595 ALOGE_IF(!silent,
596 "%s: spans overlap horizontally prev=%p, cur=%p",
597 name, prev, cur);
598 result = false;
599 }
600 } else if (cur->top < prev->bottom) {
601 ALOGE_IF(!silent,
602 "%s: spans overlap vertically prev=%p, cur=%p",
Mathias Agopian20f68782009-05-11 00:03:41 -0700603 name, prev, cur);
604 result = false;
605 }
Mathias Agopian068d47f2012-09-11 18:56:23 -0700606 prev = cur;
Mathias Agopian20f68782009-05-11 00:03:41 -0700607 }
Mathias Agopian20f68782009-05-11 00:03:41 -0700608 cur++;
609 }
610 if (b != reg.getBounds()) {
611 result = false;
Mathias Agopian068d47f2012-09-11 18:56:23 -0700612 ALOGE_IF(!silent,
613 "%s: invalid bounds [%d,%d,%d,%d] vs. [%d,%d,%d,%d]", name,
Mathias Agopian20f68782009-05-11 00:03:41 -0700614 b.left, b.top, b.right, b.bottom,
615 reg.getBounds().left, reg.getBounds().top,
616 reg.getBounds().right, reg.getBounds().bottom);
617 }
Mathias Agopian3ab68552012-08-31 14:31:40 -0700618 if (reg.mStorage.size() == 2) {
Mathias Agopian068d47f2012-09-11 18:56:23 -0700619 result = false;
620 ALOGE_IF(!silent, "%s: mStorage size is 2, which is never valid", name);
Mathias Agopian3ab68552012-08-31 14:31:40 -0700621 }
Chong Zhang639a1e12019-04-22 14:01:20 -0700622#if defined(VALIDATE_REGIONS)
Mathias Agopian068d47f2012-09-11 18:56:23 -0700623 if (result == false && !silent) {
Mathias Agopian20f68782009-05-11 00:03:41 -0700624 reg.dump(name);
Mathias Agopiancab25d62013-03-21 17:12:40 -0700625 CallStack stack(LOG_TAG);
Mathias Agopian20f68782009-05-11 00:03:41 -0700626 }
Chong Zhang639a1e12019-04-22 14:01:20 -0700627#endif
Mathias Agopian20f68782009-05-11 00:03:41 -0700628 return result;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800629}
630
Colin Cross8f279962016-09-26 13:08:16 -0700631void Region::boolean_operation(uint32_t op, Region& dst,
Mathias Agopian20f68782009-05-11 00:03:41 -0700632 const Region& lhs,
633 const Region& rhs, int dx, int dy)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800634{
Chong Zhang639a1e12019-04-22 14:01:20 -0700635#if defined(VALIDATE_REGIONS)
Mathias Agopiand0b55c02011-03-16 23:18:07 -0700636 validate(lhs, "boolean_operation (before): lhs");
637 validate(rhs, "boolean_operation (before): rhs");
638 validate(dst, "boolean_operation (before): dst");
639#endif
640
Mathias Agopian20f68782009-05-11 00:03:41 -0700641 size_t lhs_count;
642 Rect const * const lhs_rects = lhs.getArray(&lhs_count);
643
644 size_t rhs_count;
645 Rect const * const rhs_rects = rhs.getArray(&rhs_count);
646
647 region_operator<Rect>::region lhs_region(lhs_rects, lhs_count);
648 region_operator<Rect>::region rhs_region(rhs_rects, rhs_count, dx, dy);
649 region_operator<Rect> operation(op, lhs_region, rhs_region);
650 { // scope for rasterizer (dtor has side effects)
651 rasterizer r(dst);
652 operation(r);
653 }
654
Chong Zhang639a1e12019-04-22 14:01:20 -0700655#if defined(VALIDATE_REGIONS)
Mathias Agopian20f68782009-05-11 00:03:41 -0700656 validate(lhs, "boolean_operation: lhs");
657 validate(rhs, "boolean_operation: rhs");
658 validate(dst, "boolean_operation: dst");
659#endif
660
661#if VALIDATE_WITH_CORECG
662 SkRegion sk_lhs;
663 SkRegion sk_rhs;
664 SkRegion sk_dst;
665
666 for (size_t i=0 ; i<lhs_count ; i++)
667 sk_lhs.op(
668 lhs_rects[i].left + dx,
669 lhs_rects[i].top + dy,
670 lhs_rects[i].right + dx,
671 lhs_rects[i].bottom + dy,
672 SkRegion::kUnion_Op);
673
674 for (size_t i=0 ; i<rhs_count ; i++)
675 sk_rhs.op(
676 rhs_rects[i].left + dx,
677 rhs_rects[i].top + dy,
678 rhs_rects[i].right + dx,
679 rhs_rects[i].bottom + dy,
680 SkRegion::kUnion_Op);
681
682 const char* name = "---";
683 SkRegion::Op sk_op;
684 switch (op) {
685 case op_or: sk_op = SkRegion::kUnion_Op; name="OR"; break;
Romain Guyb8a2e982012-02-07 17:04:34 -0800686 case op_xor: sk_op = SkRegion::kUnion_XOR; name="XOR"; break;
Mathias Agopian20f68782009-05-11 00:03:41 -0700687 case op_and: sk_op = SkRegion::kIntersect_Op; name="AND"; break;
688 case op_nand: sk_op = SkRegion::kDifference_Op; name="NAND"; break;
689 }
690 sk_dst.op(sk_lhs, sk_rhs, sk_op);
691
692 if (sk_dst.isEmpty() && dst.isEmpty())
693 return;
694
695 bool same = true;
696 Region::const_iterator head = dst.begin();
697 Region::const_iterator const tail = dst.end();
698 SkRegion::Iterator it(sk_dst);
699 while (!it.done()) {
700 if (head != tail) {
701 if (
702 head->left != it.rect().fLeft ||
703 head->top != it.rect().fTop ||
704 head->right != it.rect().fRight ||
705 head->bottom != it.rect().fBottom
706 ) {
707 same = false;
708 break;
709 }
710 } else {
711 same = false;
712 break;
713 }
714 head++;
715 it.next();
716 }
717
718 if (head != tail) {
719 same = false;
720 }
721
722 if(!same) {
Steve Block9d453682011-12-20 16:23:08 +0000723 ALOGD("---\nregion boolean %s failed", name);
Mathias Agopian20f68782009-05-11 00:03:41 -0700724 lhs.dump("lhs");
725 rhs.dump("rhs");
726 dst.dump("dst");
Steve Block9d453682011-12-20 16:23:08 +0000727 ALOGD("should be");
Mathias Agopian20f68782009-05-11 00:03:41 -0700728 SkRegion::Iterator it(sk_dst);
729 while (!it.done()) {
Steve Block9d453682011-12-20 16:23:08 +0000730 ALOGD(" [%3d, %3d, %3d, %3d]",
Mathias Agopian20f68782009-05-11 00:03:41 -0700731 it.rect().fLeft,
732 it.rect().fTop,
733 it.rect().fRight,
734 it.rect().fBottom);
735 it.next();
736 }
737 }
738#endif
739}
740
Colin Cross8f279962016-09-26 13:08:16 -0700741void Region::boolean_operation(uint32_t op, Region& dst,
Mathias Agopian20f68782009-05-11 00:03:41 -0700742 const Region& lhs,
743 const Rect& rhs, int dx, int dy)
744{
Dan Stoza5065a552015-03-17 16:23:42 -0700745 // We allow this particular flavor of invalid Rect, since it is used as a
746 // signal value in various parts of the system
747 if (!rhs.isValid() && rhs != Rect::INVALID_RECT) {
Steve Blocke6f43dd2012-01-06 19:20:56 +0000748 ALOGE("Region::boolean_operation(op=%d) invalid Rect={%d,%d,%d,%d}",
Mathias Agopian04504522011-09-19 16:12:08 -0700749 op, rhs.left, rhs.top, rhs.right, rhs.bottom);
Mathias Agopian0857c8f2011-09-26 15:58:20 -0700750 return;
Mathias Agopian04504522011-09-19 16:12:08 -0700751 }
752
Chong Zhang639a1e12019-04-22 14:01:20 -0700753#if VALIDATE_WITH_CORECG || defined(VALIDATE_REGIONS)
Mathias Agopian20f68782009-05-11 00:03:41 -0700754 boolean_operation(op, dst, lhs, Region(rhs), dx, dy);
755#else
756 size_t lhs_count;
757 Rect const * const lhs_rects = lhs.getArray(&lhs_count);
758
759 region_operator<Rect>::region lhs_region(lhs_rects, lhs_count);
760 region_operator<Rect>::region rhs_region(&rhs, 1, dx, dy);
761 region_operator<Rect> operation(op, lhs_region, rhs_region);
762 { // scope for rasterizer (dtor has side effects)
763 rasterizer r(dst);
764 operation(r);
765 }
766
767#endif
768}
769
Colin Cross8f279962016-09-26 13:08:16 -0700770void Region::boolean_operation(uint32_t op, Region& dst,
Mathias Agopian20f68782009-05-11 00:03:41 -0700771 const Region& lhs, const Region& rhs)
772{
773 boolean_operation(op, dst, lhs, rhs, 0, 0);
774}
775
Colin Cross8f279962016-09-26 13:08:16 -0700776void Region::boolean_operation(uint32_t op, Region& dst,
Mathias Agopian20f68782009-05-11 00:03:41 -0700777 const Region& lhs, const Rect& rhs)
778{
779 boolean_operation(op, dst, lhs, rhs, 0, 0);
780}
781
782void Region::translate(Region& reg, int dx, int dy)
783{
Mathias Agopian4c0a1702012-08-31 12:45:33 -0700784 if ((dx || dy) && !reg.isEmpty()) {
Chong Zhang639a1e12019-04-22 14:01:20 -0700785#if defined(VALIDATE_REGIONS)
Mathias Agopian20f68782009-05-11 00:03:41 -0700786 validate(reg, "translate (before)");
787#endif
Mathias Agopian20f68782009-05-11 00:03:41 -0700788 size_t count = reg.mStorage.size();
789 Rect* rects = reg.mStorage.editArray();
790 while (count) {
Mathias Agopian6c7f25a2013-05-09 20:37:10 -0700791 rects->offsetBy(dx, dy);
Mathias Agopian20f68782009-05-11 00:03:41 -0700792 rects++;
793 count--;
794 }
Chong Zhang639a1e12019-04-22 14:01:20 -0700795#if defined(VALIDATE_REGIONS)
Mathias Agopian20f68782009-05-11 00:03:41 -0700796 validate(reg, "translate (after)");
797#endif
798 }
799}
800
801void Region::translate(Region& dst, const Region& reg, int dx, int dy)
802{
803 dst = reg;
804 translate(dst, dx, dy);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800805}
806
807// ----------------------------------------------------------------------------
808
Mathias Agopiane1424282013-07-29 21:24:40 -0700809size_t Region::getFlattenedSize() const {
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700810 return sizeof(uint32_t) + mStorage.size() * sizeof(Rect);
Mathias Agopian8683fca2012-08-12 19:37:16 -0700811}
812
Mathias Agopiane1424282013-07-29 21:24:40 -0700813status_t Region::flatten(void* buffer, size_t size) const {
Chong Zhang639a1e12019-04-22 14:01:20 -0700814#if defined(VALIDATE_REGIONS)
Mathias Agopian068d47f2012-09-11 18:56:23 -0700815 validate(*this, "Region::flatten");
816#endif
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700817 if (size < getFlattenedSize()) {
Mathias Agopiane1424282013-07-29 21:24:40 -0700818 return NO_MEMORY;
819 }
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700820 // Cast to uint32_t since the size of a size_t can vary between 32- and
821 // 64-bit processes
822 FlattenableUtils::write(buffer, size, static_cast<uint32_t>(mStorage.size()));
823 for (auto rect : mStorage) {
824 status_t result = rect.flatten(buffer, size);
825 if (result != NO_ERROR) {
826 return result;
827 }
828 FlattenableUtils::advance(buffer, size, sizeof(rect));
829 }
Mathias Agopian8683fca2012-08-12 19:37:16 -0700830 return NO_ERROR;
831}
832
833status_t Region::unflatten(void const* buffer, size_t size) {
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700834 if (size < sizeof(uint32_t)) {
835 return NO_MEMORY;
Mathias Agopian20f68782009-05-11 00:03:41 -0700836 }
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700837
838 uint32_t numRects = 0;
839 FlattenableUtils::read(buffer, size, numRects);
840 if (size < numRects * sizeof(Rect)) {
841 return NO_MEMORY;
842 }
843
Pablo Ceballos1a65fcc2016-07-13 14:11:57 -0700844 if (numRects > (UINT32_MAX / sizeof(Rect))) {
Yi Kong48d76082019-03-24 02:01:06 -0700845 android_errorWriteWithInfoLog(0x534e4554, "29983260", -1, nullptr, 0);
Pablo Ceballos1a65fcc2016-07-13 14:11:57 -0700846 return NO_MEMORY;
847 }
848
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700849 Region result;
850 result.mStorage.clear();
851 for (size_t r = 0; r < numRects; ++r) {
Pablo Ceballos60d69222015-08-07 14:47:20 -0700852 Rect rect(Rect::EMPTY_RECT);
Dan Stoza6fbefbb2015-03-23 13:46:14 -0700853 status_t status = rect.unflatten(buffer, size);
854 if (status != NO_ERROR) {
855 return status;
856 }
857 FlattenableUtils::advance(buffer, size, sizeof(rect));
858 result.mStorage.push_back(rect);
859 }
860
Chong Zhang639a1e12019-04-22 14:01:20 -0700861#if defined(VALIDATE_REGIONS)
Mathias Agopian068d47f2012-09-11 18:56:23 -0700862 validate(result, "Region::unflatten");
Mathias Agopian3ab68552012-08-31 14:31:40 -0700863#endif
Mathias Agopian068d47f2012-09-11 18:56:23 -0700864
865 if (!result.validate(result, "Region::unflatten", true)) {
866 ALOGE("Region::unflatten() failed, invalid region");
867 return BAD_VALUE;
868 }
869 mStorage = result.mStorage;
Mathias Agopian8683fca2012-08-12 19:37:16 -0700870 return NO_ERROR;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800871}
872
Mathias Agopian20f68782009-05-11 00:03:41 -0700873// ----------------------------------------------------------------------------
874
875Region::const_iterator Region::begin() const {
Mathias Agopian3ab68552012-08-31 14:31:40 -0700876 return mStorage.array();
Mathias Agopian20f68782009-05-11 00:03:41 -0700877}
878
879Region::const_iterator Region::end() const {
Dan Stoza2d023062018-04-09 12:14:55 -0700880 // Workaround for b/77643177
881 // mStorage should never be empty, but somehow it is and it's causing
882 // an abort in ubsan
883 if (mStorage.isEmpty()) return mStorage.array();
884
Mathias Agopian3ab68552012-08-31 14:31:40 -0700885 size_t numRects = isRect() ? 1 : mStorage.size() - 1;
886 return mStorage.array() + numRects;
Mathias Agopian20f68782009-05-11 00:03:41 -0700887}
888
889Rect const* Region::getArray(size_t* count) const {
Dan Stozad3182402014-11-17 12:03:59 -0800890 if (count) *count = static_cast<size_t>(end() - begin());
891 return begin();
Mathias Agopian20f68782009-05-11 00:03:41 -0700892}
893
Mathias Agopian20f68782009-05-11 00:03:41 -0700894// ----------------------------------------------------------------------------
895
Yiwei Zhang5434a782018-12-05 18:06:32 -0800896void Region::dump(std::string& out, const char* what, uint32_t /* flags */) const {
Mathias Agopian20f68782009-05-11 00:03:41 -0700897 const_iterator head = begin();
898 const_iterator const tail = end();
899
Yiwei Zhang5434a782018-12-05 18:06:32 -0800900 StringAppendF(&out, " Region %s (this=%p, count=%" PRIdPTR ")\n", what, this, tail - head);
Mathias Agopian20f68782009-05-11 00:03:41 -0700901 while (head != tail) {
Yiwei Zhang5434a782018-12-05 18:06:32 -0800902 StringAppendF(&out, " [%3d, %3d, %3d, %3d]\n", head->left, head->top, head->right,
903 head->bottom);
Dan Stozad3182402014-11-17 12:03:59 -0800904 ++head;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800905 }
906}
907
Dan Stozad3182402014-11-17 12:03:59 -0800908void Region::dump(const char* what, uint32_t /* flags */) const
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800909{
Mathias Agopian20f68782009-05-11 00:03:41 -0700910 const_iterator head = begin();
911 const_iterator const tail = end();
Mark Salyzyn92dc3fc2014-03-12 13:12:44 -0700912 ALOGD(" Region %s (this=%p, count=%" PRIdPTR ")\n", what, this, tail-head);
Mathias Agopian20f68782009-05-11 00:03:41 -0700913 while (head != tail) {
Steve Block9d453682011-12-20 16:23:08 +0000914 ALOGD(" [%3d, %3d, %3d, %3d]\n",
Mathias Agopian20f68782009-05-11 00:03:41 -0700915 head->left, head->top, head->right, head->bottom);
916 head++;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800917 }
918}
919
920// ----------------------------------------------------------------------------
921
922}; // namespace android