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
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/policy/remote_commands/device_command_screenshot_job.h"
10 #include "base/bind.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/sequenced_worker_pool.h"
17 #include "base/values.h"
18 #include "chrome/browser/chromeos/policy/upload_job_impl.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "net/http/http_request_headers.h"
21 #include "policy/proto/device_management_backend.pb.h"
27 // Determines the time, measured from the time of issue, after which the command
28 // queue will consider this command expired if the command has not been started.
29 const int kCommandExpirationTimeInMinutes
= 10;
31 // String constant identifying the result field in the result payload.
32 const char* const kResultFieldName
= "result";
34 // Template string constant for populating the name field.
35 const char* const kNameFieldTemplate
= "Screen %d";
37 // Template string constant for populating the name field.
38 const char* const kFilenameFieldTemplate
= "screen%d.png";
40 // String constant identifying the header field which stores the command id.
41 const char* const kCommandIdHeaderName
= "Command-ID";
43 // String constant signalling that the segment contains a png image.
44 const char* const kContentTypeImagePng
= "image/png";
46 // String constant identifying the header field which stores the file type.
47 const char* const kFileTypeHeaderName
= "File-Type";
49 // String constant signalling that the data segment contains screenshots.
50 const char* const kFileTypeScreenshotFile
= "screenshot_file";
52 // String constant identifying the upload url field in the command payload.
53 const char* const kUploadUrlFieldName
= "fileUploadUrl";
55 // A helper function which invokes |store_screenshot_callback| on |task_runner|.
56 void RunStoreScreenshotOnTaskRunner(
57 const ui::GrabWindowSnapshotAsyncPNGCallback
& store_screenshot_callback
,
58 scoped_refptr
<base::TaskRunner
> task_runner
,
59 scoped_refptr
<base::RefCountedBytes
> png_data
) {
60 task_runner
->PostTask(FROM_HERE
,
61 base::Bind(store_screenshot_callback
, png_data
));
66 class DeviceCommandScreenshotJob::Payload
67 : public RemoteCommandJob::ResultPayload
{
69 explicit Payload(ResultCode result_code
);
70 ~Payload() override
{}
72 // RemoteCommandJob::ResultPayload:
73 scoped_ptr
<std::string
> Serialize() override
;
78 DISALLOW_COPY_AND_ASSIGN(Payload
);
81 DeviceCommandScreenshotJob::Payload::Payload(ResultCode result_code
) {
82 base::DictionaryValue root_dict
;
83 if (result_code
!= SUCCESS
)
84 root_dict
.SetInteger(kResultFieldName
, result_code
);
85 base::JSONWriter::Write(root_dict
, &payload_
);
88 scoped_ptr
<std::string
> DeviceCommandScreenshotJob::Payload::Serialize() {
89 return make_scoped_ptr(new std::string(payload_
));
92 DeviceCommandScreenshotJob::DeviceCommandScreenshotJob(
93 scoped_ptr
<Delegate
> screenshot_delegate
)
94 : num_pending_screenshots_(0),
95 screenshot_delegate_(screenshot_delegate
.Pass()),
96 weak_ptr_factory_(this) {
97 DCHECK(screenshot_delegate_
);
100 DeviceCommandScreenshotJob::~DeviceCommandScreenshotJob() {
103 enterprise_management::RemoteCommand_Type
DeviceCommandScreenshotJob::GetType()
105 return enterprise_management::RemoteCommand_Type_DEVICE_SCREENSHOT
;
108 void DeviceCommandScreenshotJob::OnSuccess() {
109 base::ThreadTaskRunnerHandle::Get()->PostTask(
111 base::Bind(succeeded_callback_
,
112 base::Passed(make_scoped_ptr(new Payload(SUCCESS
)))));
115 void DeviceCommandScreenshotJob::OnFailure(UploadJob::ErrorCode error_code
) {
116 ResultCode result_code
= FAILURE_CLIENT
;
117 switch (error_code
) {
118 case UploadJob::AUTHENTICATION_ERROR
:
119 result_code
= FAILURE_AUTHENTICATION
;
121 case UploadJob::NETWORK_ERROR
:
122 case UploadJob::SERVER_ERROR
:
123 result_code
= FAILURE_SERVER
;
125 case UploadJob::CONTENT_ENCODING_ERROR
:
126 result_code
= FAILURE_CLIENT
;
129 base::ThreadTaskRunnerHandle::Get()->PostTask(
131 base::Bind(failed_callback_
,
132 base::Passed(make_scoped_ptr(new Payload(result_code
)))));
135 bool DeviceCommandScreenshotJob::IsExpired(base::TimeTicks now
) {
136 return now
> issued_time() + base::TimeDelta::FromMinutes(
137 kCommandExpirationTimeInMinutes
);
140 bool DeviceCommandScreenshotJob::ParseCommandPayload(
141 const std::string
& command_payload
) {
142 scoped_ptr
<base::Value
> root(base::JSONReader().ReadToValue(command_payload
));
145 base::DictionaryValue
* payload
= nullptr;
146 if (!root
->GetAsDictionary(&payload
))
148 std::string upload_url
;
149 if (!payload
->GetString(kUploadUrlFieldName
, &upload_url
))
151 upload_url_
= GURL(upload_url
);
155 void DeviceCommandScreenshotJob::StoreScreenshot(
157 scoped_refptr
<base::RefCountedBytes
> png_data
) {
158 screenshots_
.insert(std::make_pair(screen
, png_data
));
159 DCHECK_LT(0, num_pending_screenshots_
);
160 --num_pending_screenshots_
;
162 if (num_pending_screenshots_
== 0)
163 StartScreenshotUpload();
166 void DeviceCommandScreenshotJob::StartScreenshotUpload() {
167 for (const auto& screenshot_entry
: screenshots_
) {
168 std::map
<std::string
, std::string
> header_fields
;
169 header_fields
.insert(
170 std::make_pair(kFileTypeHeaderName
, kFileTypeScreenshotFile
));
171 header_fields
.insert(std::make_pair(net::HttpRequestHeaders::kContentType
,
172 kContentTypeImagePng
));
173 header_fields
.insert(std::make_pair(kCommandIdHeaderName
,
174 base::Uint64ToString(unique_id())));
175 scoped_ptr
<std::string
> data
= make_scoped_ptr(
176 new std::string((const char*)screenshot_entry
.second
->front(),
177 screenshot_entry
.second
->size()));
178 upload_job_
->AddDataSegment(
179 base::StringPrintf(kNameFieldTemplate
, screenshot_entry
.first
),
180 base::StringPrintf(kFilenameFieldTemplate
, screenshot_entry
.first
),
181 header_fields
, data
.Pass());
183 upload_job_
->Start();
186 void DeviceCommandScreenshotJob::RunImpl(
187 const CallbackWithResult
& succeeded_callback
,
188 const CallbackWithResult
& failed_callback
) {
189 succeeded_callback_
= succeeded_callback
;
190 failed_callback_
= failed_callback
;
192 // Fail if the delegate says screenshots are not allowed in this session.
193 if (!screenshot_delegate_
->IsScreenshotAllowed()) {
194 base::ThreadTaskRunnerHandle::Get()->PostTask(
196 base::Bind(failed_callback_
, base::Passed(make_scoped_ptr(
197 new Payload(FAILURE_USER_INPUT
)))));
200 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
202 // Immediately fail if the upload url is invalid.
203 if (!upload_url_
.is_valid()) {
204 LOG(ERROR
) << upload_url_
<< " is not a valid URL.";
205 base::ThreadTaskRunnerHandle::Get()->PostTask(
207 base::Bind(failed_callback_
, base::Passed(make_scoped_ptr(
208 new Payload(FAILURE_INVALID_URL
)))));
212 // Immediately fail if there are no attached screens.
213 if (root_windows
.size() == 0) {
214 base::ThreadTaskRunnerHandle::Get()->PostTask(
216 base::Bind(failed_callback_
, base::Passed(make_scoped_ptr(new Payload(
217 FAILURE_SCREENSHOT_ACQUISITION
)))));
221 upload_job_
= screenshot_delegate_
->CreateUploadJob(upload_url_
, this);
224 // Post tasks to the sequenced worker pool for taking screenshots on each
226 num_pending_screenshots_
= root_windows
.size();
227 for (size_t screen
= 0; screen
< root_windows
.size(); ++screen
) {
228 aura::Window
* root_window
= root_windows
[screen
];
229 gfx::Rect rect
= root_window
->bounds();
230 screenshot_delegate_
->TakeSnapshot(
232 base::Bind(&RunStoreScreenshotOnTaskRunner
,
233 base::Bind(&DeviceCommandScreenshotJob::StoreScreenshot
,
234 weak_ptr_factory_
.GetWeakPtr(), screen
),
235 base::ThreadTaskRunnerHandle::Get()));
239 void DeviceCommandScreenshotJob::TerminateImpl() {
240 weak_ptr_factory_
.InvalidateWeakPtrs();
243 } // namespace policy