1 // Copyright 2016 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/ui/webui/settings/chromeos/device_storage_handler.h"
6 
7 #include <algorithm>
8 #include <limits>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 
13 #include "chrome/browser/chromeos/arc/arc_util.h"
14 #include "chrome/browser/chromeos/file_manager/path_util.h"
15 #include "chrome/browser/platform_util.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "chromeos/disks/disk.h"
18 #include "components/arc/arc_features.h"
19 #include "content/public/browser/web_ui_data_source.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/text/bytes_formatting.h"
22 
23 using chromeos::disks::Disk;
24 using chromeos::disks::DiskMountManager;
25 
26 namespace chromeos {
27 namespace settings {
28 
29 namespace {
30 
31 constexpr char kAndroidEnabled[] = "androidEnabled";
32 
CalculationTypeToEventName(calculator::SizeCalculator::CalculationType x)33 const char* CalculationTypeToEventName(
34     calculator::SizeCalculator::CalculationType x) {
35   switch (x) {
36     case calculator::SizeCalculator::CalculationType::kSystem:
37       return "storage-system-size-changed";
38     case calculator::SizeCalculator::CalculationType::kInUse:
39       return "storage-size-stat-changed";
40     case calculator::SizeCalculator::CalculationType::kMyFiles:
41       return "storage-my-files-size-changed";
42     case calculator::SizeCalculator::CalculationType::kBrowsingData:
43       return "storage-browsing-data-size-changed";
44     case calculator::SizeCalculator::CalculationType::kAppsExtensions:
45       return "storage-apps-size-changed";
46     case calculator::SizeCalculator::CalculationType::kCrostini:
47       return "storage-crostini-size-changed";
48     case calculator::SizeCalculator::CalculationType::kOtherUsers:
49       return "storage-other-users-size-changed";
50   }
51   NOTREACHED();
52   return "";
53 }
54 
55 }  // namespace
56 
StorageHandler(Profile * profile,content::WebUIDataSource * html_source)57 StorageHandler::StorageHandler(Profile* profile,
58                                content::WebUIDataSource* html_source)
59     : size_stat_calculator_(profile),
60       my_files_size_calculator_(profile),
61       browsing_data_size_calculator_(profile),
62       apps_size_calculator_(profile),
63       crostini_size_calculator_(profile),
64       other_users_size_calculator_(),
65       profile_(profile),
66       source_name_(html_source->GetSource()),
67       arc_observer_(this),
68       special_volume_path_pattern_("[a-z]+://.*") {
69   html_source->AddBoolean(
70       kAndroidEnabled,
71       base::FeatureList::IsEnabled(arc::kUsbStorageUIFeature) &&
72           arc::IsArcPlayStoreEnabledForProfile(profile));
73 }
74 
~StorageHandler()75 StorageHandler::~StorageHandler() {
76   StopObservingEvents();
77 }
78 
RegisterMessages()79 void StorageHandler::RegisterMessages() {
80   DCHECK(web_ui());
81 
82   web_ui()->RegisterMessageCallback(
83       "updateAndroidEnabled",
84       base::BindRepeating(&StorageHandler::HandleUpdateAndroidEnabled,
85                           base::Unretained(this)));
86   web_ui()->RegisterMessageCallback(
87       "updateStorageInfo",
88       base::BindRepeating(&StorageHandler::HandleUpdateStorageInfo,
89                           base::Unretained(this)));
90   web_ui()->RegisterMessageCallback(
91       "openMyFiles", base::BindRepeating(&StorageHandler::HandleOpenMyFiles,
92                                          base::Unretained(this)));
93   web_ui()->RegisterMessageCallback(
94       "openArcStorage",
95       base::BindRepeating(&StorageHandler::HandleOpenArcStorage,
96                           base::Unretained(this)));
97   web_ui()->RegisterMessageCallback(
98       "updateExternalStorages",
99       base::BindRepeating(&StorageHandler::HandleUpdateExternalStorages,
100                           base::Unretained(this)));
101 }
102 
OnJavascriptAllowed()103 void StorageHandler::OnJavascriptAllowed() {
104   if (base::FeatureList::IsEnabled(arc::kUsbStorageUIFeature))
105     arc_observer_.Add(arc::ArcSessionManager::Get());
106 
107   // Start observing mount/unmount events to update the connected device list.
108   DiskMountManager::GetInstance()->AddObserver(this);
109 
110   // Start observing calculators.
111   size_stat_calculator_.AddObserver(this);
112   my_files_size_calculator_.AddObserver(this);
113   browsing_data_size_calculator_.AddObserver(this);
114   apps_size_calculator_.AddObserver(this);
115   crostini_size_calculator_.AddObserver(this);
116   other_users_size_calculator_.AddObserver(this);
117 }
118 
OnJavascriptDisallowed()119 void StorageHandler::OnJavascriptDisallowed() {
120   // Ensure that pending callbacks do not complete and cause JS to be evaluated.
121   weak_ptr_factory_.InvalidateWeakPtrs();
122 
123   if (base::FeatureList::IsEnabled(arc::kUsbStorageUIFeature))
124     arc_observer_.Remove(arc::ArcSessionManager::Get());
125 
126   StopObservingEvents();
127 }
128 
RoundByteSize(int64_t bytes)129 int64_t StorageHandler::RoundByteSize(int64_t bytes) {
130   if (bytes < 0) {
131     NOTREACHED() << "Negative bytes value";
132     return -1;
133   }
134 
135   // Subtract one to the original number of bytes.
136   bytes--;
137   // Set all the lower bits to 1.
138   bytes |= bytes >> 1;
139   bytes |= bytes >> 2;
140   bytes |= bytes >> 4;
141   bytes |= bytes >> 8;
142   bytes |= bytes >> 16;
143   bytes |= bytes >> 32;
144   // Add one. The one bit beyond the highest set bit is set to 1. All the lower
145   // bits are set to 0.
146   bytes++;
147 
148   return bytes;
149 }
150 
HandleUpdateAndroidEnabled(const base::ListValue * unused_args)151 void StorageHandler::HandleUpdateAndroidEnabled(
152     const base::ListValue* unused_args) {
153   // OnJavascriptAllowed() calls ArcSessionManager::AddObserver() later.
154   AllowJavascript();
155 }
156 
HandleUpdateStorageInfo(const base::ListValue * args)157 void StorageHandler::HandleUpdateStorageInfo(const base::ListValue* args) {
158   AllowJavascript();
159 
160   size_stat_calculator_.StartCalculation();
161   my_files_size_calculator_.StartCalculation();
162   browsing_data_size_calculator_.StartCalculation();
163   apps_size_calculator_.StartCalculation();
164   crostini_size_calculator_.StartCalculation();
165   other_users_size_calculator_.StartCalculation();
166 }
167 
HandleOpenMyFiles(const base::ListValue * unused_args)168 void StorageHandler::HandleOpenMyFiles(const base::ListValue* unused_args) {
169   const base::FilePath my_files_path =
170       file_manager::util::GetMyFilesFolderForProfile(profile_);
171   platform_util::OpenItem(profile_, my_files_path, platform_util::OPEN_FOLDER,
172                           platform_util::OpenOperationCallback());
173 }
174 
HandleOpenArcStorage(const base::ListValue * unused_args)175 void StorageHandler::HandleOpenArcStorage(
176     const base::ListValue* unused_args) {
177   auto* arc_storage_manager =
178       arc::ArcStorageManager::GetForBrowserContext(profile_);
179   if (arc_storage_manager)
180     arc_storage_manager->OpenPrivateVolumeSettings();
181 }
182 
HandleUpdateExternalStorages(const base::ListValue * unused_args)183 void StorageHandler::HandleUpdateExternalStorages(
184     const base::ListValue* unused_args) {
185   UpdateExternalStorages();
186 }
187 
UpdateExternalStorages()188 void StorageHandler::UpdateExternalStorages() {
189   base::Value devices(base::Value::Type::LIST);
190   for (const auto& itr : DiskMountManager::GetInstance()->mount_points()) {
191     const DiskMountManager::MountPointInfo& mount_info = itr.second;
192     if (!IsEligibleForAndroidStorage(mount_info.source_path))
193       continue;
194 
195     const chromeos::disks::Disk* disk =
196         DiskMountManager::GetInstance()->FindDiskBySourcePath(
197             mount_info.source_path);
198     if (!disk)
199       continue;
200 
201     std::string label = disk->device_label();
202     if (label.empty()) {
203       // To make volume labels consistent with Files app, we follow how Files
204       // generates a volume label when the volume doesn't have specific label.
205       // That is, we use the base name of mount path instead in such cases.
206       // TODO(fukino): Share the implementation to compute the volume name with
207       // Files app. crbug.com/1002535.
208       label = base::FilePath(mount_info.mount_path).BaseName().AsUTF8Unsafe();
209     }
210     base::Value device(base::Value::Type::DICTIONARY);
211     device.SetKey("uuid", base::Value(disk->fs_uuid()));
212     device.SetKey("label", base::Value(label));
213     devices.Append(std::move(device));
214   }
215   FireWebUIListener("onExternalStoragesUpdated", devices);
216 }
217 
OnArcPlayStoreEnabledChanged(bool enabled)218 void StorageHandler::OnArcPlayStoreEnabledChanged(bool enabled) {
219   auto update = std::make_unique<base::DictionaryValue>();
220   update->SetKey(kAndroidEnabled, base::Value(enabled));
221   content::WebUIDataSource::Update(profile_, source_name_, std::move(update));
222 }
223 
OnMountEvent(DiskMountManager::MountEvent event,chromeos::MountError error_code,const DiskMountManager::MountPointInfo & mount_info)224 void StorageHandler::OnMountEvent(
225     DiskMountManager::MountEvent event,
226     chromeos::MountError error_code,
227     const DiskMountManager::MountPointInfo& mount_info) {
228   if (error_code != chromeos::MountError::MOUNT_ERROR_NONE)
229     return;
230 
231   if (!IsEligibleForAndroidStorage(mount_info.source_path))
232     return;
233 
234   UpdateExternalStorages();
235 }
236 
OnSizeCalculated(const calculator::SizeCalculator::CalculationType & calculation_type,int64_t total_bytes,const base::Optional<int64_t> & available_bytes)237 void StorageHandler::OnSizeCalculated(
238     const calculator::SizeCalculator::CalculationType& calculation_type,
239     int64_t total_bytes,
240     const base::Optional<int64_t>& available_bytes) {
241   if (available_bytes) {
242     UpdateSizeStat(calculation_type, total_bytes, available_bytes.value());
243   } else {
244     UpdateStorageItem(calculation_type, total_bytes);
245   }
246 }
247 
StopObservingEvents()248 void StorageHandler::StopObservingEvents() {
249   // Stop observing mount/unmount events to update the connected device list.
250   DiskMountManager::GetInstance()->RemoveObserver(this);
251 
252   // Stop observing calculators.
253   size_stat_calculator_.RemoveObserver(this);
254   my_files_size_calculator_.RemoveObserver(this);
255   browsing_data_size_calculator_.RemoveObserver(this);
256   apps_size_calculator_.RemoveObserver(this);
257   crostini_size_calculator_.RemoveObserver(this);
258   other_users_size_calculator_.RemoveObserver(this);
259 }
260 
UpdateStorageItem(const calculator::SizeCalculator::CalculationType & calculation_type,int64_t total_bytes)261 void StorageHandler::UpdateStorageItem(
262     const calculator::SizeCalculator::CalculationType& calculation_type,
263     int64_t total_bytes) {
264   // When the system size has been calculated, UpdateSystemSize calls this
265   // method with the calculation type kSystem. This check prevents an infinite
266   // loop.
267   if (calculation_type != calculator::SizeCalculator::CalculationType::kSystem)
268     UpdateSystemSize(calculation_type, total_bytes);
269 
270   base::string16 message;
271   if (total_bytes < 0) {
272     message = l10n_util::GetStringUTF16(IDS_SETTINGS_STORAGE_SIZE_UNKNOWN);
273   } else {
274     message = ui::FormatBytes(total_bytes);
275   }
276 
277   if (calculation_type ==
278       calculator::SizeCalculator::CalculationType::kOtherUsers) {
279     bool no_other_users = (total_bytes == 0);
280     FireWebUIListener(CalculationTypeToEventName(calculation_type),
281                       base::Value(message), base::Value(no_other_users));
282   } else {
283     FireWebUIListener(CalculationTypeToEventName(calculation_type),
284                       base::Value(message));
285   }
286 }
287 
UpdateSizeStat(const calculator::SizeCalculator::CalculationType & calculation_type,int64_t total_bytes,int64_t available_bytes)288 void StorageHandler::UpdateSizeStat(
289     const calculator::SizeCalculator::CalculationType& calculation_type,
290     int64_t total_bytes,
291     int64_t available_bytes) {
292   int64_t rounded_total_bytes = RoundByteSize(total_bytes);
293   int64_t in_use_total_bytes_ = rounded_total_bytes - available_bytes;
294 
295   UpdateSystemSize(calculation_type, in_use_total_bytes_);
296 
297   base::DictionaryValue size_stat;
298   size_stat.SetString("availableSize", ui::FormatBytes(available_bytes));
299   size_stat.SetString("usedSize", ui::FormatBytes(in_use_total_bytes_));
300   size_stat.SetDouble("usedRatio", static_cast<double>(in_use_total_bytes_) /
301                                        rounded_total_bytes);
302   int storage_space_state =
303       static_cast<int>(StorageSpaceState::kStorageSpaceNormal);
304   if (available_bytes < kSpaceCriticallyLowBytes)
305     storage_space_state =
306         static_cast<int>(StorageSpaceState::kStorageSpaceCriticallyLow);
307   else if (available_bytes < kSpaceLowBytes)
308     storage_space_state = static_cast<int>(StorageSpaceState::kStorageSpaceLow);
309   size_stat.SetInteger("spaceState", storage_space_state);
310 
311   FireWebUIListener(CalculationTypeToEventName(calculation_type), size_stat);
312 }
313 
UpdateSystemSize(const calculator::SizeCalculator::CalculationType & calculation_type,int64_t total_bytes)314 void StorageHandler::UpdateSystemSize(
315     const calculator::SizeCalculator::CalculationType& calculation_type,
316     int64_t total_bytes) {
317   const int item_index = static_cast<int>(calculation_type);
318   storage_items_total_bytes_[item_index] = total_bytes > 0 ? total_bytes : 0;
319   calculation_state_.set(item_index);
320 
321   // Update system size. We only display the total system size when the size of
322   // all categories has been updated. If some size calculations are pending,
323   // return early and wait for all calculations to complete.
324   if (!calculation_state_.all())
325     return;
326 
327   int64_t system_bytes = 0;
328   for (int i = 0; i < calculator::SizeCalculator::kCalculationTypeCount; ++i) {
329     int64_t total_bytes_for_current_item = storage_items_total_bytes_[i];
330     // If the storage is in use, add to the system's total storage.
331     if (i ==
332         static_cast<int>(calculator::SizeCalculator::CalculationType::kInUse)) {
333       system_bytes += total_bytes_for_current_item;
334       continue;
335     }
336     // Otherwise, this storage amount counts against the total storage
337     // amount.
338     system_bytes -= total_bytes_for_current_item;
339   }
340 
341   OnSizeCalculated(calculator::SizeCalculator::CalculationType::kSystem,
342                    system_bytes);
343 }
344 
IsEligibleForAndroidStorage(std::string source_path)345 bool StorageHandler::IsEligibleForAndroidStorage(std::string source_path) {
346   // Android's StorageManager volume concept relies on assumption that it is
347   // local filesystem. Hence, special volumes like DriveFS should not be
348   // listed on the Settings.
349   return !RE2::FullMatch(source_path, special_volume_path_pattern_);
350 }
351 
352 }  // namespace settings
353 }  // namespace chromeos
354