OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/android/data_usage/data_use_ui_tab_model.h" | 5 #include "chrome/browser/android/data_usage/data_use_ui_tab_model.h" |
6 | 6 |
7 #include <utility> | 7 #include <utility> |
8 | 8 |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/memory/ref_counted.h" | 10 #include "base/memory/ref_counted.h" |
11 #include "base/single_thread_task_runner.h" | |
12 #include "content/public/browser/browser_thread.h" | 11 #include "content/public/browser/browser_thread.h" |
13 #include "url/gurl.h" | 12 #include "url/gurl.h" |
14 | 13 |
14 namespace { | |
15 | |
16 // Notifies |data_use_tab_model| of navigation event on IO thread. | |
17 void OnNavigationEventOnIOThread( | |
18 base::WeakPtr<chrome::android::DataUseTabModel> data_use_tab_model, | |
sclittle
2015/11/17 22:50:11
For readability, you could move this anonymous nam
tbansal1
2015/11/18 01:32:23
Done.
| |
19 int32_t tab_id, | |
20 chrome::android::DataUseTabModel::TransitionType transition, | |
21 const GURL gurl) { | |
22 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
23 | |
24 if (!data_use_tab_model) | |
25 return; | |
26 data_use_tab_model->OnNavigationEvent(tab_id, transition, gurl); | |
27 } | |
28 | |
29 // Notifies |data_use_tab_model| of tab closure on IO thread. | |
30 void OnTabCloseEventOnIOThread( | |
31 base::WeakPtr<chrome::android::DataUseTabModel> data_use_tab_model, | |
32 int32_t tab_id) { | |
33 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
34 | |
35 if (!data_use_tab_model) | |
36 return; | |
37 data_use_tab_model->OnTabCloseEvent(tab_id); | |
38 } | |
39 | |
40 // Notifies |data_use_ui_tab_model| on UI thread that tracking has started on | |
41 // tab with id |tab_id|. | |
42 void NotifyTrackingStartingOnUIThread( | |
43 base::WeakPtr<chrome::android::DataUseUITabModel> | data_use_ui_tab_model | | |
mmenke
2015/11/17 22:48:40
|?
tbansal1
2015/11/18 01:32:23
Done.
| |
44 , | |
sclittle
2015/11/17 22:50:11
I don't think this compiles, please fix this.
tbansal1
2015/11/18 01:32:23
Done.
| |
45 int32_t tab_id) { | |
46 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
47 if (!data_use_ui_tab_model) | |
48 return; | |
49 data_use_ui_tab_model->NotifyTrackingStarting(tab_id); | |
50 } | |
51 | |
52 // Notifies |data_use_ui_tab_model| on UI thread that tracking has ended on tab | |
53 // with id |tab_id|. | |
54 void NotifyTrackingEndingOnUIThread( | |
55 base::WeakPtr<chrome::android::DataUseUITabModel> data_use_ui_tab_model, | |
56 int32_t tab_id) { | |
57 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
58 if (!data_use_ui_tab_model) | |
59 return; | |
60 data_use_ui_tab_model->NotifyTrackingEnding(tab_id); | |
61 } | |
62 | |
63 // Sets TabObserverOnIOThread by calling DataUseUITabModel's method on UI | |
64 // thread. | |
65 void SetTabDataUseObserverOnUIThread( | |
66 base::WeakPtr<chrome::android::DataUseUITabModel> data_use_ui_tab_model, | |
67 scoped_refptr<chrome::android::DataUseUITabModel::TabObserverOnIOThread> | |
68 tab_data_use_observer) { | |
69 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
70 | |
71 if (!data_use_ui_tab_model) | |
72 return; | |
73 data_use_ui_tab_model->SetTabDataUseObserver(tab_data_use_observer); | |
74 } | |
75 | |
76 // Creates a TabObserverOnIOThread object on IO thread. | |
77 void CreateTabObserverOnIOThread( | |
78 base::WeakPtr<chrome::android::DataUseUITabModel> data_use_ui_tab_model, | |
79 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { | |
80 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
81 scoped_refptr<chrome::android::DataUseUITabModel::TabObserverOnIOThread> | |
82 tab_observer( | |
83 new chrome::android::DataUseUITabModel::TabObserverOnIOThread( | |
84 data_use_ui_tab_model, ui_task_runner)); | |
85 ui_task_runner->PostTask(FROM_HERE, | |
86 base::Bind(&SetTabDataUseObserverOnUIThread, | |
87 data_use_ui_tab_model, tab_observer)); | |
88 } | |
89 | |
90 // Passes |data_use_tab_model| to |tab_data_use_observer| on IO thread. | |
91 void SetDataUseTabModelForTabDataUseObserver( | |
92 scoped_refptr<chrome::android::DataUseUITabModel::TabObserverOnIOThread> | |
93 tab_data_use_observer, | |
94 base::WeakPtr<chrome::android::DataUseTabModel> data_use_tab_model) { | |
95 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
96 | |
97 if (tab_data_use_observer) | |
98 tab_data_use_observer->SetIODataUseTabModel(data_use_tab_model); | |
99 } | |
100 | |
101 } // namespace | |
102 | |
15 namespace chrome { | 103 namespace chrome { |
16 | 104 |
17 namespace android { | 105 namespace android { |
18 | 106 |
19 DataUseUITabModel::DataUseUITabModel( | 107 DataUseUITabModel::DataUseUITabModel( |
20 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) | 108 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
21 : io_task_runner_(io_task_runner) { | 109 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
sclittle
2015/11/17 22:50:11
Instead of taking in a ui_task_runner, you could j
tbansal1
2015/11/18 01:32:23
Done.
| |
110 : io_task_runner_(io_task_runner), weak_factory_(this) { | |
111 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
22 DCHECK(io_task_runner_); | 112 DCHECK(io_task_runner_); |
113 DCHECK(ui_task_runner); | |
114 DCHECK(ui_task_runner->BelongsToCurrentThread()); | |
115 | |
116 io_task_runner_->PostTask( | |
sclittle
2015/11/17 22:50:11
It might just be simpler to have CreateTabObserver
tbansal1
2015/11/18 01:32:23
Done.
| |
117 FROM_HERE, base::Bind(&CreateTabObserverOnIOThread, | |
118 weak_factory_.GetWeakPtr(), ui_task_runner)); | |
23 } | 119 } |
24 | 120 |
25 DataUseUITabModel::~DataUseUITabModel() {} | 121 DataUseUITabModel::~DataUseUITabModel() { |
122 if (tab_data_use_observer_) { | |
123 DCHECK(thread_checker_.CalledOnValidThread()); | |
124 tab_data_use_observer_->AddRef(); | |
125 | |
126 // Removes the last refptr to |tab_observer|, so that it is destroyed on IO | |
sclittle
2015/11/17 22:50:11
nit: Say "Removes the last reference" instead of "
tbansal1
2015/11/18 01:32:23
Done.
| |
127 // thread. | |
128 io_task_runner_->ReleaseSoon(FROM_HERE, tab_data_use_observer_.get()); | |
129 tab_data_use_observer_ = nullptr; | |
130 } | |
131 } | |
26 | 132 |
27 void DataUseUITabModel::ReportBrowserNavigation( | 133 void DataUseUITabModel::ReportBrowserNavigation( |
28 const GURL& gurl, | 134 const GURL& gurl, |
29 ui::PageTransition page_transition, | 135 ui::PageTransition page_transition, |
30 int32_t tab_id) const { | 136 int32_t tab_id) const { |
31 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 137 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
32 DCHECK(thread_checker_.CalledOnValidThread()); | 138 DCHECK(thread_checker_.CalledOnValidThread()); |
33 | 139 |
34 // TODO(tbansal): Post to DataUseTabModel on IO thread. | 140 if (tab_id < 0) |
sclittle
2015/11/17 22:50:11
For clarity, test for the -1 tab ID at the point w
tbansal1
2015/11/18 01:32:23
Done.
| |
141 return; | |
142 | |
143 DataUseTabModel::TransitionType transition_type; | |
144 | |
145 if (ConvertTransitionType(page_transition, &transition_type)) { | |
146 io_task_runner_->PostTask( | |
sclittle
2015/11/17 22:50:11
PostTask already has built-in support for cancelli
tbansal1
2015/11/18 01:32:22
Done.
| |
147 FROM_HERE, | |
148 base::Bind(&OnNavigationEventOnIOThread, io_data_use_tab_model_, tab_id, | |
149 transition_type, gurl)); | |
150 } | |
35 } | 151 } |
36 | 152 |
37 void DataUseUITabModel::ReportTabClosure(int32_t tab_id) { | 153 void DataUseUITabModel::ReportTabClosure(int32_t tab_id) { |
38 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 154 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
39 DCHECK(thread_checker_.CalledOnValidThread()); | 155 DCHECK(thread_checker_.CalledOnValidThread()); |
40 | 156 |
41 // TODO(tbansal): Post to DataUseTabModel on IO thread. | 157 if (tab_id < 0) |
158 return; | |
159 | |
160 io_task_runner_->PostTask( | |
sclittle
2015/11/17 22:50:11
Same here, just PostTask directly on the DataUseTa
tbansal1
2015/11/18 01:32:23
Done.
| |
161 FROM_HERE, | |
162 base::Bind(&OnTabCloseEventOnIOThread, io_data_use_tab_model_, tab_id)); | |
42 | 163 |
43 // Clear out local state. | 164 // Clear out local state. |
44 TabEvents::iterator it = tab_events_.find(tab_id); | 165 TabEvents::iterator it = tab_events_.find(tab_id); |
45 if (it == tab_events_.end()) | 166 if (it == tab_events_.end()) |
46 return; | 167 return; |
47 tab_events_.erase(it); | 168 tab_events_.erase(it); |
48 } | 169 } |
49 | 170 |
50 void DataUseUITabModel::ReportCustomTabInitialNavigation( | 171 void DataUseUITabModel::ReportCustomTabInitialNavigation( |
51 int32_t tab_id, | 172 int32_t tab_id, |
52 const std::string& url, | 173 const std::string& url, |
53 const std::string& package_name) { | 174 const std::string& package_name) { |
54 // TODO(tbansal): Post to DataUseTabModel on IO thread. | 175 // TODO(tbansal): Post to DataUseTabModel on IO thread. |
55 } | 176 } |
56 | 177 |
57 void DataUseUITabModel::OnTrackingStarted(int32_t tab_id) { | 178 void DataUseUITabModel::SetIODataUseTabModel( |
179 base::WeakPtr<DataUseTabModel> io_data_use_tab_model) { | |
58 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 180 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
59 DCHECK(thread_checker_.CalledOnValidThread()); | 181 DCHECK(thread_checker_.CalledOnValidThread()); |
60 | 182 |
183 // It is okay to use base::Unretained because this is only called at the time | |
184 // of profile creation. | |
185 io_data_use_tab_model_ = io_data_use_tab_model; | |
186 io_task_runner_->PostTask( | |
187 FROM_HERE, base::Bind(&SetDataUseTabModelForTabDataUseObserver, | |
sclittle
2015/11/17 22:50:11
Just post a task to create the observer here with
tbansal1
2015/11/18 01:32:23
Done.
| |
188 tab_data_use_observer_, io_data_use_tab_model_)); | |
189 } | |
190 | |
191 void DataUseUITabModel::NotifyTrackingStarting(int32_t tab_id) { | |
192 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
193 DCHECK(thread_checker_.CalledOnValidThread()); | |
194 | |
61 if (MaybeCreateTabEvent(tab_id, DATA_USE_TRACKING_STARTED)) | 195 if (MaybeCreateTabEvent(tab_id, DATA_USE_TRACKING_STARTED)) |
62 return; | 196 return; |
63 // Since tracking started before the UI could indicate that it ended, it is | 197 // Since tracking started before the UI could indicate that it ended, it is |
64 // not useful for UI to show that it started again. | 198 // not useful for UI to show that it started again. |
65 RemoveTabEvent(tab_id, DATA_USE_TRACKING_ENDED); | 199 RemoveTabEvent(tab_id, DATA_USE_TRACKING_ENDED); |
66 } | 200 } |
67 | 201 |
68 void DataUseUITabModel::OnTrackingEnded(int32_t tab_id) { | 202 void DataUseUITabModel::NotifyTrackingEnding(int32_t tab_id) { |
69 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 203 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
70 DCHECK(thread_checker_.CalledOnValidThread()); | 204 DCHECK(thread_checker_.CalledOnValidThread()); |
71 | 205 |
72 if (MaybeCreateTabEvent(tab_id, DATA_USE_TRACKING_ENDED)) | 206 if (MaybeCreateTabEvent(tab_id, DATA_USE_TRACKING_ENDED)) |
73 return; | 207 return; |
74 // Since tracking ended before the UI could indicate that it stated, it is not | 208 // Since tracking ended before the UI could indicate that it stated, it is not |
75 // useful for UI to show that it ended. | 209 // useful for UI to show that it ended. |
76 RemoveTabEvent(tab_id, DATA_USE_TRACKING_STARTED); | 210 RemoveTabEvent(tab_id, DATA_USE_TRACKING_STARTED); |
77 } | 211 } |
78 | 212 |
79 bool DataUseUITabModel::HasDataUseTrackingStarted(int32_t tab_id) { | 213 bool DataUseUITabModel::HasDataUseTrackingStarted(int32_t tab_id) { |
80 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 214 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
sclittle
2015/11/17 22:50:11
nit: Just use the thread_checker everywhere, since
tbansal1
2015/11/18 01:32:23
Done.
| |
81 DCHECK(thread_checker_.CalledOnValidThread()); | 215 DCHECK(thread_checker_.CalledOnValidThread()); |
82 | 216 |
83 TabEvents::iterator it = tab_events_.find(tab_id); | 217 TabEvents::iterator it = tab_events_.find(tab_id); |
84 if (it == tab_events_.end()) | 218 if (it == tab_events_.end()) |
85 return false; | 219 return false; |
86 | 220 |
87 return RemoveTabEvent(tab_id, DATA_USE_TRACKING_STARTED); | 221 return RemoveTabEvent(tab_id, DATA_USE_TRACKING_STARTED); |
88 } | 222 } |
89 | 223 |
90 bool DataUseUITabModel::HasDataUseTrackingEnded(int32_t tab_id) { | 224 bool DataUseUITabModel::HasDataUseTrackingEnded(int32_t tab_id) { |
91 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 225 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
92 DCHECK(thread_checker_.CalledOnValidThread()); | 226 DCHECK(thread_checker_.CalledOnValidThread()); |
93 | 227 |
94 TabEvents::iterator it = tab_events_.find(tab_id); | 228 TabEvents::iterator it = tab_events_.find(tab_id); |
95 if (it == tab_events_.end()) | 229 if (it == tab_events_.end()) |
96 return false; | 230 return false; |
97 | 231 |
98 return RemoveTabEvent(tab_id, DATA_USE_TRACKING_ENDED); | 232 return RemoveTabEvent(tab_id, DATA_USE_TRACKING_ENDED); |
99 } | 233 } |
100 | 234 |
235 | |
236 void DataUseUITabModel::SetTabDataUseObserver( | |
237 scoped_refptr<TabObserverOnIOThread> tab_data_use_observer) { | |
238 DCHECK(thread_checker_.CalledOnValidThread()); | |
sclittle
2015/11/17 22:50:11
Should you also DCHECK that this method is only ev
tbansal1
2015/11/18 01:32:23
Added DCHECK
| |
239 tab_data_use_observer_.swap(tab_data_use_observer); | |
sclittle
2015/11/17 22:50:11
Why swap? Why not just assign it? The previous val
tbansal1
2015/11/18 01:32:23
Done.
| |
240 DCHECK(tab_data_use_observer_); | |
241 } | |
242 | |
101 bool DataUseUITabModel::MaybeCreateTabEvent(int32_t tab_id, | 243 bool DataUseUITabModel::MaybeCreateTabEvent(int32_t tab_id, |
102 DataUseTrackingEvent event) { | 244 DataUseTrackingEvent event) { |
245 DCHECK(thread_checker_.CalledOnValidThread()); | |
103 TabEvents::iterator it = tab_events_.find(tab_id); | 246 TabEvents::iterator it = tab_events_.find(tab_id); |
104 if (it == tab_events_.end()) { | 247 if (it == tab_events_.end()) { |
105 tab_events_.insert(std::make_pair(tab_id, event)); | 248 tab_events_.insert(std::make_pair(tab_id, event)); |
106 return true; | 249 return true; |
107 } | 250 } |
108 return false; | 251 return false; |
109 } | 252 } |
110 | 253 |
111 bool DataUseUITabModel::RemoveTabEvent(int32_t tab_id, | 254 bool DataUseUITabModel::RemoveTabEvent(int32_t tab_id, |
112 DataUseTrackingEvent event) { | 255 DataUseTrackingEvent event) { |
256 DCHECK(thread_checker_.CalledOnValidThread()); | |
113 TabEvents::iterator it = tab_events_.find(tab_id); | 257 TabEvents::iterator it = tab_events_.find(tab_id); |
114 DCHECK(it != tab_events_.end()); | 258 DCHECK(it != tab_events_.end()); |
115 if (it->second == event) { | 259 if (it->second == event) { |
116 tab_events_.erase(it); | 260 tab_events_.erase(it); |
117 return true; | 261 return true; |
118 } | 262 } |
119 return false; | 263 return false; |
120 } | 264 } |
121 | 265 |
266 bool DataUseUITabModel::ConvertTransitionType( | |
267 ui::PageTransition page_transition, | |
268 DataUseTabModel::TransitionType* transition_type) const { | |
269 if (!ui::PageTransitionIsValidType(page_transition) || | |
270 !ui::PageTransitionIsMainFrame(page_transition) || | |
271 !ui::PageTransitionIsNewNavigation(page_transition)) { | |
272 return false; | |
273 } | |
274 | |
275 const int32_t mask = 0xFFFFFFFF ^ ui::PAGE_TRANSITION_QUALIFIER_MASK; | |
276 | |
277 switch (page_transition & mask) { | |
278 case ui::PAGE_TRANSITION_LINK: | |
279 if ((page_transition & ui::PAGE_TRANSITION_FROM_API) != 0) { | |
280 // Clicking on bookmarks. | |
281 *transition_type = DataUseTabModel::TRANSITION_BOOKMARK; | |
282 return true; | |
283 } | |
284 return false; // Newtab, clicking on a link. | |
285 case ui::PAGE_TRANSITION_TYPED: | |
286 *transition_type = DataUseTabModel::TRANSITION_OMNIBOX_NAVIGATION; | |
287 return true; | |
288 case ui::PAGE_TRANSITION_AUTO_BOOKMARK: | |
289 // Auto bookmark from newtab page. | |
290 *transition_type = DataUseTabModel::TRANSITION_BOOKMARK; | |
291 return true; | |
292 case ui::PAGE_TRANSITION_AUTO_TOPLEVEL: | |
293 // History menu. | |
294 *transition_type = DataUseTabModel::TRANSITION_HISTORY_ITEM; | |
295 return true; | |
296 case ui::PAGE_TRANSITION_GENERATED: | |
297 // Omnibox search (e.g., searching for "tacos"). | |
298 *transition_type = DataUseTabModel::TRANSITION_OMNIBOX_SEARCH; | |
299 return true; | |
300 case ui::PAGE_TRANSITION_RELOAD: | |
301 // Restored tabs. | |
302 return false; | |
303 default: | |
304 return false; | |
305 } | |
306 } | |
307 | |
308 DataUseUITabModel::TabObserverOnIOThread::TabObserverOnIOThread( | |
309 base::WeakPtr<DataUseUITabModel> data_use_ui_tab_model, | |
310 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) | |
311 : data_use_ui_tab_model_(data_use_ui_tab_model), | |
312 ui_task_runner_(ui_task_runner), | |
313 registered_as_observer_(false) { | |
314 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
315 DCHECK(ui_task_runner_); | |
316 } | |
317 | |
318 DataUseUITabModel::TabObserverOnIOThread::~TabObserverOnIOThread() { | |
319 DCHECK(thread_checker_.CalledOnValidThread()); | |
320 if (data_use_tab_model_ && registered_as_observer_) | |
321 data_use_tab_model_->RemoveObserver(this); | |
322 } | |
323 | |
324 void DataUseUITabModel::TabObserverOnIOThread::SetIODataUseTabModel( | |
325 base::WeakPtr<DataUseTabModel> data_use_tab_model) { | |
326 DCHECK(thread_checker_.CalledOnValidThread()); | |
327 | |
328 data_use_tab_model_ = data_use_tab_model; | |
329 | |
330 if (registered_as_observer_) | |
331 return; | |
sclittle
2015/11/17 22:50:11
This is weird. It looks like this Observer support
tbansal1
2015/11/18 01:32:23
Done.
| |
332 registered_as_observer_ = true; | |
333 | |
334 if (data_use_tab_model_) | |
335 data_use_tab_model_->AddObserver(this); | |
sclittle
2015/11/17 22:50:11
You should just DCHECK(data_use_tab_model_) when y
tbansal1
2015/11/18 01:32:23
Done.
| |
336 } | |
337 | |
338 void DataUseUITabModel::TabObserverOnIOThread::NotifyTrackingStarting( | |
339 int32_t tab_id) { | |
340 DCHECK(thread_checker_.CalledOnValidThread()); | |
341 | |
342 ui_task_runner_->PostTask(FROM_HERE, | |
sclittle
2015/11/17 22:50:11
PostTask already has built-in support for cancelli
tbansal1
2015/11/18 01:32:23
Done.
| |
343 base::Bind(&NotifyTrackingStartingOnUIThread, | |
344 data_use_ui_tab_model_, tab_id)); | |
345 } | |
346 | |
347 void DataUseUITabModel::TabObserverOnIOThread::NotifyTrackingEnding( | |
348 int32_t tab_id) { | |
349 DCHECK(thread_checker_.CalledOnValidThread()); | |
350 | |
351 ui_task_runner_->PostTask(FROM_HERE, | |
352 base::Bind(&NotifyTrackingEndingOnUIThread, | |
353 data_use_ui_tab_model_, tab_id)); | |
354 } | |
355 | |
122 } // namespace android | 356 } // namespace android |
123 | 357 |
124 } // namespace chrome | 358 } // namespace chrome |
OLD | NEW |