1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/android/compositor/tab_content_manager.h"
6 
7 #include <android/bitmap.h>
8 #include <stddef.h>
9 
10 #include <algorithm>
11 #include <memory>
12 #include <utility>
13 
14 #include "base/android/callback_android.h"
15 #include "base/android/jni_android.h"
16 #include "base/android/jni_string.h"
17 #include "base/android/scoped_java_ref.h"
18 #include "base/bind.h"
19 #include "base/callback_helpers.h"
20 #include "base/macros.h"
21 #include "base/metrics/field_trial_params.h"
22 #include "cc/layers/layer.h"
23 #include "chrome/android/chrome_jni_headers/TabContentManager_jni.h"
24 #include "chrome/browser/android/compositor/layer/thumbnail_layer.h"
25 #include "chrome/browser/android/tab_android.h"
26 #include "chrome/browser/flags/android/chrome_feature_list.h"
27 #include "chrome/browser/thumbnail/cc/thumbnail.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host.h"
31 #include "content/public/browser/render_widget_host_view.h"
32 #include "content/public/browser/web_contents.h"
33 #include "skia/ext/image_operations.h"
34 #include "ui/android/resources/ui_resource_provider.h"
35 #include "ui/android/view_android.h"
36 #include "ui/gfx/android/java_bitmap.h"
37 #include "ui/gfx/geometry/dip_util.h"
38 #include "ui/gfx/geometry/rect.h"
39 #include "url/gurl.h"
40 
41 using base::android::JavaParamRef;
42 using base::android::JavaRef;
43 
44 namespace {
45 
46 using TabReadbackCallback = base::OnceCallback<void(float, const SkBitmap&)>;
47 
48 }  // namespace
49 
50 namespace android {
51 
52 class TabContentManager::TabReadbackRequest {
53  public:
TabReadbackRequest(content::RenderWidgetHostView * rwhv,float thumbnail_scale,bool crop_to_match_aspect_ratio,TabReadbackCallback end_callback)54   TabReadbackRequest(content::RenderWidgetHostView* rwhv,
55                      float thumbnail_scale,
56                      bool crop_to_match_aspect_ratio,
57                      TabReadbackCallback end_callback)
58       : thumbnail_scale_(thumbnail_scale),
59         end_callback_(std::move(end_callback)),
60         drop_after_readback_(false) {
61     DCHECK(rwhv);
62     auto result_callback =
63         base::BindOnce(&TabReadbackRequest::OnFinishGetTabThumbnailBitmap,
64                        weak_factory_.GetWeakPtr());
65 
66     gfx::Size view_size_in_pixels =
67         rwhv->GetNativeView()->GetPhysicalBackingSize();
68     if (view_size_in_pixels.IsEmpty()) {
69       std::move(result_callback).Run(SkBitmap());
70       return;
71     }
72     if (crop_to_match_aspect_ratio) {
73       double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
74           chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio",
75           1.0);
76       aspect_ratio = ThumbnailCache::clampAspectRatio(aspect_ratio, 0.5, 2.0);
77       int height = std::min(view_size_in_pixels.height(),
78                             (int)(view_size_in_pixels.width() / aspect_ratio));
79       view_size_in_pixels.set_height(height);
80     }
81     gfx::Rect source_rect = gfx::Rect(view_size_in_pixels);
82     gfx::Size thumbnail_size(
83         gfx::ScaleToCeiledSize(view_size_in_pixels, thumbnail_scale_));
84     rwhv->CopyFromSurface(source_rect, thumbnail_size,
85                           std::move(result_callback));
86   }
87 
~TabReadbackRequest()88   virtual ~TabReadbackRequest() {}
89 
OnFinishGetTabThumbnailBitmap(const SkBitmap & bitmap)90   void OnFinishGetTabThumbnailBitmap(const SkBitmap& bitmap) {
91     if (bitmap.drawsNothing() || drop_after_readback_) {
92       std::move(end_callback_).Run(0.f, SkBitmap());
93       return;
94     }
95 
96     SkBitmap result_bitmap = bitmap;
97     result_bitmap.setImmutable();
98     std::move(end_callback_).Run(thumbnail_scale_, bitmap);
99   }
100 
SetToDropAfterReadback()101   void SetToDropAfterReadback() { drop_after_readback_ = true; }
102 
103  private:
104   const float thumbnail_scale_;
105   TabReadbackCallback end_callback_;
106   bool drop_after_readback_;
107 
108   base::WeakPtrFactory<TabReadbackRequest> weak_factory_{this};
109 
110   DISALLOW_COPY_AND_ASSIGN(TabReadbackRequest);
111 };
112 
113 // static
FromJavaObject(const JavaRef<jobject> & jobj)114 TabContentManager* TabContentManager::FromJavaObject(
115     const JavaRef<jobject>& jobj) {
116   if (jobj.is_null())
117     return nullptr;
118   return reinterpret_cast<TabContentManager*>(
119       Java_TabContentManager_getNativePtr(base::android::AttachCurrentThread(),
120                                           jobj));
121 }
122 
TabContentManager(JNIEnv * env,jobject obj,jint default_cache_size,jint approximation_cache_size,jint compression_queue_max_size,jint write_queue_max_size,jboolean use_approximation_thumbnail,jboolean save_jpeg_thumbnails)123 TabContentManager::TabContentManager(JNIEnv* env,
124                                      jobject obj,
125                                      jint default_cache_size,
126                                      jint approximation_cache_size,
127                                      jint compression_queue_max_size,
128                                      jint write_queue_max_size,
129                                      jboolean use_approximation_thumbnail,
130                                      jboolean save_jpeg_thumbnails)
131     : weak_java_tab_content_manager_(env, obj) {
132   double jpeg_aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
133       chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", 1.0);
134   thumbnail_cache_ = std::make_unique<ThumbnailCache>(
135       static_cast<size_t>(default_cache_size),
136       static_cast<size_t>(approximation_cache_size),
137       static_cast<size_t>(compression_queue_max_size),
138       static_cast<size_t>(write_queue_max_size), use_approximation_thumbnail,
139       save_jpeg_thumbnails, jpeg_aspect_ratio);
140   thumbnail_cache_->AddThumbnailCacheObserver(this);
141 }
142 
~TabContentManager()143 TabContentManager::~TabContentManager() {
144 }
145 
Destroy(JNIEnv * env)146 void TabContentManager::Destroy(JNIEnv* env) {
147   thumbnail_cache_->RemoveThumbnailCacheObserver(this);
148   delete this;
149 }
150 
SetUIResourceProvider(ui::UIResourceProvider * ui_resource_provider)151 void TabContentManager::SetUIResourceProvider(
152     ui::UIResourceProvider* ui_resource_provider) {
153   thumbnail_cache_->SetUIResourceProvider(ui_resource_provider);
154 }
155 
GetLiveLayer(int tab_id)156 scoped_refptr<cc::Layer> TabContentManager::GetLiveLayer(int tab_id) {
157   return live_layer_list_[tab_id];
158 }
159 
GetStaticLayer(int tab_id)160 scoped_refptr<ThumbnailLayer> TabContentManager::GetStaticLayer(int tab_id) {
161   return static_layer_cache_[tab_id];
162 }
163 
GetOrCreateStaticLayer(int tab_id,bool force_disk_read)164 scoped_refptr<ThumbnailLayer> TabContentManager::GetOrCreateStaticLayer(
165     int tab_id,
166     bool force_disk_read) {
167   Thumbnail* thumbnail = thumbnail_cache_->Get(tab_id, force_disk_read, true);
168   scoped_refptr<ThumbnailLayer> static_layer = static_layer_cache_[tab_id];
169 
170   if (!thumbnail || !thumbnail->ui_resource_id()) {
171     if (static_layer.get()) {
172       static_layer->layer()->RemoveFromParent();
173       static_layer_cache_.erase(tab_id);
174     }
175     return nullptr;
176   }
177 
178   if (!static_layer.get()) {
179     static_layer = ThumbnailLayer::Create();
180     static_layer_cache_[tab_id] = static_layer;
181   }
182 
183   static_layer->SetThumbnail(thumbnail);
184   return static_layer;
185 }
186 
AttachTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & jtab,jint tab_id)187 void TabContentManager::AttachTab(JNIEnv* env,
188                                   const JavaParamRef<jobject>& obj,
189                                   const JavaParamRef<jobject>& jtab,
190                                   jint tab_id) {
191   TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
192   scoped_refptr<cc::Layer> layer = tab->GetContentLayer();
193   if (!layer.get())
194     return;
195 
196   scoped_refptr<cc::Layer> cached_layer = live_layer_list_[tab_id];
197   if (cached_layer != layer)
198     live_layer_list_[tab_id] = layer;
199 }
200 
DetachTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & jtab,jint tab_id)201 void TabContentManager::DetachTab(JNIEnv* env,
202                                   const JavaParamRef<jobject>& obj,
203                                   const JavaParamRef<jobject>& jtab,
204                                   jint tab_id) {
205   scoped_refptr<cc::Layer> current_layer = live_layer_list_[tab_id];
206   if (!current_layer.get()) {
207     // Empty cached layer should not exist but it is ok if it happens.
208     return;
209   }
210 
211   TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
212   scoped_refptr<cc::Layer> layer = tab->GetContentLayer();
213   // We need to remove if we're getting a detach for our current layer or we're
214   // getting a detach with NULL and we have a current layer, which means remove
215   //  all layers.
216   if (current_layer.get() &&
217       (layer.get() == current_layer.get() || !layer.get())) {
218     live_layer_list_.erase(tab_id);
219   }
220 }
221 
HasFullCachedThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id)222 jboolean TabContentManager::HasFullCachedThumbnail(
223     JNIEnv* env,
224     const JavaParamRef<jobject>& obj,
225     jint tab_id) {
226   return thumbnail_cache_->Get(tab_id, false, false) != nullptr;
227 }
228 
GetRwhvForTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab)229 content::RenderWidgetHostView* TabContentManager::GetRwhvForTab(
230     JNIEnv* env,
231     const JavaParamRef<jobject>& obj,
232     const JavaParamRef<jobject>& tab) {
233   TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
234   DCHECK(tab_android);
235   const int tab_id = tab_android->GetAndroidId();
236   if (pending_tab_readbacks_.find(tab_id) != pending_tab_readbacks_.end()) {
237     return nullptr;
238   }
239 
240   content::WebContents* web_contents = tab_android->web_contents();
241   DCHECK(web_contents);
242 
243   content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
244   if (!rvh)
245     return nullptr;
246 
247   content::RenderWidgetHost* rwh = rvh->GetWidget();
248   content::RenderWidgetHostView* rwhv = rwh ? rwh->GetView() : nullptr;
249   if (!rwhv || !rwhv->IsSurfaceAvailableForCopy())
250     return nullptr;
251 
252   return rwhv;
253 }
254 
CaptureThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab,jfloat thumbnail_scale,jboolean write_to_cache,const base::android::JavaParamRef<jobject> & j_callback)255 void TabContentManager::CaptureThumbnail(
256     JNIEnv* env,
257     const JavaParamRef<jobject>& obj,
258     const JavaParamRef<jobject>& tab,
259     jfloat thumbnail_scale,
260     jboolean write_to_cache,
261     const base::android::JavaParamRef<jobject>& j_callback) {
262   TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
263   DCHECK(tab_android);
264   const int tab_id = tab_android->GetAndroidId();
265 
266   content::RenderWidgetHostView* rwhv = GetRwhvForTab(env, obj, tab);
267   if (!rwhv) {
268     if (j_callback)
269       RunObjectCallbackAndroid(j_callback, nullptr);
270     return;
271   }
272   if (write_to_cache && !thumbnail_cache_->CheckAndUpdateThumbnailMetaData(
273                             tab_id, tab_android->GetURL())) {
274     return;
275   }
276   TabReadbackCallback readback_done_callback = base::BindOnce(
277       &TabContentManager::OnTabReadback, weak_factory_.GetWeakPtr(), tab_id,
278       base::android::ScopedJavaGlobalRef<jobject>(j_callback), write_to_cache);
279   pending_tab_readbacks_[tab_id] = std::make_unique<TabReadbackRequest>(
280       rwhv, thumbnail_scale, !write_to_cache,
281       std::move(readback_done_callback));
282 }
283 
CacheTabWithBitmap(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab,const JavaParamRef<jobject> & bitmap,jfloat thumbnail_scale)284 void TabContentManager::CacheTabWithBitmap(JNIEnv* env,
285                                            const JavaParamRef<jobject>& obj,
286                                            const JavaParamRef<jobject>& tab,
287                                            const JavaParamRef<jobject>& bitmap,
288                                            jfloat thumbnail_scale) {
289   TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
290   DCHECK(tab_android);
291   int tab_id = tab_android->GetAndroidId();
292   GURL url = tab_android->GetURL();
293 
294   gfx::JavaBitmap java_bitmap_lock(bitmap);
295   SkBitmap skbitmap = gfx::CreateSkBitmapFromJavaBitmap(java_bitmap_lock);
296   skbitmap.setImmutable();
297 
298   if (thumbnail_cache_->CheckAndUpdateThumbnailMetaData(tab_id, url))
299     OnTabReadback(tab_id, nullptr, true, thumbnail_scale, skbitmap);
300 }
301 
InvalidateIfChanged(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id,const JavaParamRef<jstring> & jurl)302 void TabContentManager::InvalidateIfChanged(JNIEnv* env,
303                                             const JavaParamRef<jobject>& obj,
304                                             jint tab_id,
305                                             const JavaParamRef<jstring>& jurl) {
306   thumbnail_cache_->InvalidateThumbnailIfChanged(
307       tab_id, GURL(base::android::ConvertJavaStringToUTF8(env, jurl)));
308 }
309 
UpdateVisibleIds(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jintArray> & priority,jint primary_tab_id)310 void TabContentManager::UpdateVisibleIds(
311     JNIEnv* env,
312     const JavaParamRef<jobject>& obj,
313     const JavaParamRef<jintArray>& priority,
314     jint primary_tab_id) {
315   std::list<int> priority_ids;
316   jsize length = env->GetArrayLength(priority);
317   jint* ints = env->GetIntArrayElements(priority, nullptr);
318   for (jsize i = 0; i < length; ++i)
319     priority_ids.push_back(static_cast<int>(ints[i]));
320 
321   env->ReleaseIntArrayElements(priority, ints, JNI_ABORT);
322   thumbnail_cache_->UpdateVisibleIds(priority_ids, primary_tab_id);
323 }
324 
NativeRemoveTabThumbnail(int tab_id)325 void TabContentManager::NativeRemoveTabThumbnail(int tab_id) {
326   TabReadbackRequestMap::iterator readback_iter =
327       pending_tab_readbacks_.find(tab_id);
328   if (readback_iter != pending_tab_readbacks_.end())
329     readback_iter->second->SetToDropAfterReadback();
330   thumbnail_cache_->Remove(tab_id);
331 }
332 
RemoveTabThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id)333 void TabContentManager::RemoveTabThumbnail(JNIEnv* env,
334                                            const JavaParamRef<jobject>& obj,
335                                            jint tab_id) {
336   NativeRemoveTabThumbnail(tab_id);
337 }
338 
GetEtc1TabThumbnail(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint tab_id,const base::android::JavaParamRef<jobject> & j_callback)339 void TabContentManager::GetEtc1TabThumbnail(
340     JNIEnv* env,
341     const base::android::JavaParamRef<jobject>& obj,
342     jint tab_id,
343     const base::android::JavaParamRef<jobject>& j_callback) {
344   thumbnail_cache_->DecompressThumbnailFromFile(
345       tab_id,
346       base::BindRepeating(
347           &TabContentManager::SendThumbnailToJava, weak_factory_.GetWeakPtr(),
348           base::android::ScopedJavaGlobalRef<jobject>(j_callback),
349           /* need_downsampling */ true));
350 }
351 
OnUIResourcesWereEvicted()352 void TabContentManager::OnUIResourcesWereEvicted() {
353   thumbnail_cache_->OnUIResourcesWereEvicted();
354 }
355 
OnFinishedThumbnailRead(int tab_id)356 void TabContentManager::OnFinishedThumbnailRead(int tab_id) {
357   JNIEnv* env = base::android::AttachCurrentThread();
358   Java_TabContentManager_notifyListenersOfThumbnailChange(
359       env, weak_java_tab_content_manager_.get(env), tab_id);
360 }
361 
OnTabReadback(int tab_id,base::android::ScopedJavaGlobalRef<jobject> j_callback,bool write_to_cache,float thumbnail_scale,const SkBitmap & bitmap)362 void TabContentManager::OnTabReadback(
363     int tab_id,
364     base::android::ScopedJavaGlobalRef<jobject> j_callback,
365     bool write_to_cache,
366     float thumbnail_scale,
367     const SkBitmap& bitmap) {
368   TabReadbackRequestMap::iterator readback_iter =
369       pending_tab_readbacks_.find(tab_id);
370 
371   if (readback_iter != pending_tab_readbacks_.end())
372     pending_tab_readbacks_.erase(tab_id);
373 
374   if (j_callback) {
375     SendThumbnailToJava(j_callback, write_to_cache, true, bitmap);
376   }
377 
378   if (write_to_cache && thumbnail_scale > 0 && !bitmap.empty())
379     thumbnail_cache_->Put(tab_id, bitmap, thumbnail_scale);
380 }
381 
SendThumbnailToJava(base::android::ScopedJavaGlobalRef<jobject> j_callback,bool need_downsampling,bool result,const SkBitmap & bitmap)382 void TabContentManager::SendThumbnailToJava(
383     base::android::ScopedJavaGlobalRef<jobject> j_callback,
384     bool need_downsampling,
385     bool result,
386     const SkBitmap& bitmap) {
387   ScopedJavaLocalRef<jobject> j_bitmap;
388   if (!bitmap.isNull() && result) {
389     // In portrait mode, we want to show thumbnails in squares.
390     // Therefore, the thumbnail saved in portrait mode needs to be cropped to
391     // a square, or it would be vertically center-aligned, and the top would
392     // be hidden.
393     // It's fine to horizontally center-align thumbnail saved in landscape
394     // mode.
395     int scale = need_downsampling ? 2 : 1;
396     double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
397         chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", 1.0);
398     aspect_ratio = ThumbnailCache::clampAspectRatio(aspect_ratio, 0.5, 2.0);
399     SkIRect dest_subset = {
400         0, 0, bitmap.width() / scale,
401         std::min(bitmap.height() / scale,
402                  (int)(bitmap.width() / aspect_ratio / scale))};
403     j_bitmap = gfx::ConvertToJavaBitmap(skia::ImageOperations::Resize(
404         bitmap, skia::ImageOperations::RESIZE_BETTER, bitmap.width() / scale,
405         bitmap.height() / scale, dest_subset));
406   }
407   RunObjectCallbackAndroid(j_callback, j_bitmap);
408 }
409 
SetCaptureMinRequestTimeForTesting(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint timeMs)410 void TabContentManager::SetCaptureMinRequestTimeForTesting(
411     JNIEnv* env,
412     const base::android::JavaParamRef<jobject>& obj,
413     jint timeMs) {
414   thumbnail_cache_->SetCaptureMinRequestTimeForTesting(timeMs);
415 }
416 
GetPendingReadbacksForTesting(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj)417 jint TabContentManager::GetPendingReadbacksForTesting(
418     JNIEnv* env,
419     const base::android::JavaParamRef<jobject>& obj) {
420   return pending_tab_readbacks_.size();
421 }
422 
423 // ----------------------------------------------------------------------------
424 // Native JNI methods
425 // ----------------------------------------------------------------------------
426 
JNI_TabContentManager_Init(JNIEnv * env,const JavaParamRef<jobject> & obj,jint default_cache_size,jint approximation_cache_size,jint compression_queue_max_size,jint write_queue_max_size,jboolean use_approximation_thumbnail,jboolean save_jpeg_thumbnails)427 jlong JNI_TabContentManager_Init(JNIEnv* env,
428                                  const JavaParamRef<jobject>& obj,
429                                  jint default_cache_size,
430                                  jint approximation_cache_size,
431                                  jint compression_queue_max_size,
432                                  jint write_queue_max_size,
433                                  jboolean use_approximation_thumbnail,
434                                  jboolean save_jpeg_thumbnails) {
435   TabContentManager* manager = new TabContentManager(
436       env, obj, default_cache_size, approximation_cache_size,
437       compression_queue_max_size, write_queue_max_size,
438       use_approximation_thumbnail, save_jpeg_thumbnails);
439   return reinterpret_cast<intptr_t>(manager);
440 }
441 
442 }  // namespace android
443