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