• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenshot;
18 
19 import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
20 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
21 import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
22 import static com.android.systemui.screenshot.LogConfig.logTag;
23 import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
24 
25 import android.app.ActivityTaskManager;
26 import android.app.Notification;
27 import android.app.PendingIntent;
28 import android.content.ClipData;
29 import android.content.ClipDescription;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.UserInfo;
34 import android.content.res.Resources;
35 import android.graphics.Bitmap;
36 import android.graphics.drawable.Icon;
37 import android.net.Uri;
38 import android.os.AsyncTask;
39 import android.os.Bundle;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.provider.DeviceConfig;
45 import android.text.TextUtils;
46 import android.util.Log;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
50 import com.android.systemui.R;
51 import com.android.systemui.flags.FeatureFlags;
52 import com.android.systemui.flags.Flags;
53 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
54 
55 import com.google.common.util.concurrent.ListenableFuture;
56 
57 import java.text.DateFormat;
58 import java.util.ArrayList;
59 import java.util.Date;
60 import java.util.List;
61 import java.util.Random;
62 import java.util.UUID;
63 import java.util.concurrent.CompletableFuture;
64 import java.util.function.Supplier;
65 
66 /**
67  * An AsyncTask that saves an image to the media store in the background.
68  */
69 class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
70     private static final String TAG = logTag(SaveImageInBackgroundTask.class);
71 
72     private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
73     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
74 
75     private final Context mContext;
76     private FeatureFlags mFlags;
77     private final ScreenshotSmartActions mScreenshotSmartActions;
78     private final ScreenshotController.SaveImageInBackgroundData mParams;
79     private final ScreenshotController.SavedImageData mImageData;
80     private final ScreenshotController.QuickShareData mQuickShareData;
81 
82     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
83     private String mScreenshotId;
84     private final Random mRandom = new Random();
85     private final Supplier<ActionTransition> mSharedElementTransition;
86     private final ImageExporter mImageExporter;
87     private long mImageTime;
88 
SaveImageInBackgroundTask( Context context, FeatureFlags flags, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, Supplier<ActionTransition> sharedElementTransition, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider )89     SaveImageInBackgroundTask(
90             Context context,
91             FeatureFlags flags,
92             ImageExporter exporter,
93             ScreenshotSmartActions screenshotSmartActions,
94             ScreenshotController.SaveImageInBackgroundData data,
95             Supplier<ActionTransition> sharedElementTransition,
96             ScreenshotNotificationSmartActionsProvider
97                     screenshotNotificationSmartActionsProvider
98     ) {
99         mContext = context;
100         mFlags = flags;
101         mScreenshotSmartActions = screenshotSmartActions;
102         mImageData = new ScreenshotController.SavedImageData();
103         mQuickShareData = new ScreenshotController.QuickShareData();
104         mSharedElementTransition = sharedElementTransition;
105         mImageExporter = exporter;
106 
107         // Prepare all the output metadata
108         mParams = data;
109 
110         // Initialize screenshot notification smart actions provider.
111         mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
112     }
113 
114     @Override
doInBackground(Void... paramsUnused)115     protected Void doInBackground(Void... paramsUnused) {
116         if (isCancelled()) {
117             if (DEBUG_STORAGE) {
118                 Log.d(TAG, "cancelled! returning null");
119             }
120             return null;
121         }
122         // TODO: move to constructor / from ScreenshotRequest
123         final UUID requestId = UUID.randomUUID();
124         final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
125                 ? mParams.owner : getUserHandleOfForegroundApplication(mContext);
126 
127         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
128 
129         Bitmap image = mParams.image;
130         mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
131 
132         boolean savingToOtherUser = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
133                 && (user != Process.myUserHandle());
134         // Smart actions don't yet work for cross-user saves.
135         boolean smartActionsEnabled = !savingToOtherUser
136                 && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
137                 SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
138                 true);
139         try {
140             if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
141                 // Since Quick Share target recommendation does not rely on image URL, it is
142                 // queried and surfaced before image compress/export. Action intent would not be
143                 // used, because it does not contain image URL.
144                 queryQuickShareAction(image, user);
145             }
146 
147             // Call synchronously here since already on a background thread.
148             ListenableFuture<ImageExporter.Result> future =
149                     mImageExporter.export(Runnable::run, requestId, image, mParams.owner);
150             ImageExporter.Result result = future.get();
151             Log.d(TAG, "Saved screenshot: " + result);
152             final Uri uri = result.uri;
153             mImageTime = result.timestamp;
154 
155             CompletableFuture<List<Notification.Action>> smartActionsFuture =
156                     mScreenshotSmartActions.getSmartActionsFuture(
157                             mScreenshotId, uri, image, mSmartActionsProvider,
158                             ScreenshotSmartActionType.REGULAR_SMART_ACTIONS,
159                             smartActionsEnabled, user);
160             List<Notification.Action> smartActions = new ArrayList<>();
161             if (smartActionsEnabled) {
162                 int timeoutMs = DeviceConfig.getInt(
163                         DeviceConfig.NAMESPACE_SYSTEMUI,
164                         SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
165                         1000);
166                 smartActions.addAll(buildSmartActions(
167                         mScreenshotSmartActions.getSmartActions(
168                                 mScreenshotId, smartActionsFuture, timeoutMs,
169                                 mSmartActionsProvider,
170                                 ScreenshotSmartActionType.REGULAR_SMART_ACTIONS),
171                         mContext));
172             }
173 
174             mImageData.uri = uri;
175             mImageData.owner = user;
176             mImageData.smartActions = smartActions;
177             mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
178                     smartActionsEnabled);
179             mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
180                     smartActionsEnabled);
181             mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
182                     smartActionsEnabled);
183             mImageData.quickShareAction = createQuickShareAction(mContext,
184                     mQuickShareData.quickShareAction, uri);
185             mImageData.subject = getSubjectString();
186 
187             mParams.mActionsReadyListener.onActionsReady(mImageData);
188             if (DEBUG_CALLBACK) {
189                 Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
190                         + "finisher.accept(\"" + mImageData.uri + "\"");
191             }
192             mParams.finisher.accept(mImageData.uri);
193             mParams.image = null;
194         } catch (Exception e) {
195             // IOException/UnsupportedOperationException may be thrown if external storage is
196             // not mounted
197             if (DEBUG_STORAGE) {
198                 Log.d(TAG, "Failed to store screenshot", e);
199             }
200             mParams.clearImage();
201             mImageData.reset();
202             mQuickShareData.reset();
203             mParams.mActionsReadyListener.onActionsReady(mImageData);
204             if (DEBUG_CALLBACK) {
205                 Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
206             }
207             mParams.finisher.accept(null);
208         }
209 
210         return null;
211     }
212 
213     /**
214      * Update the listener run when the saving task completes. Used to avoid showing UI for the
215      * first screenshot when a second one is taken.
216      */
setActionsReadyListener(ScreenshotController.ActionsReadyListener listener)217     void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) {
218         mParams.mActionsReadyListener = listener;
219     }
220 
221     @Override
onCancelled(Void params)222     protected void onCancelled(Void params) {
223         // If we are cancelled while the task is running in the background, we may get null
224         // params. The finisher is expected to always be called back, so just use the baked-in
225         // params from the ctor in any case.
226         mImageData.reset();
227         mQuickShareData.reset();
228         mParams.mActionsReadyListener.onActionsReady(mImageData);
229         if (DEBUG_CALLBACK) {
230             Log.d(TAG, "onCancelled, calling (Consumer<Uri>) finisher.accept(null)");
231         }
232         mParams.finisher.accept(null);
233         mParams.clearImage();
234     }
235 
236     /**
237      * Assumes that the action intent is sent immediately after being supplied.
238      */
239     @VisibleForTesting
createShareAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled)240     Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
241             boolean smartActionsEnabled) {
242         return () -> {
243             ActionTransition transition = mSharedElementTransition.get();
244 
245             // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
246             // order to do some common work like dismissing the keyguard and sending
247             // closeSystemWindows
248 
249             // Create a share intent, this will always go through the chooser activity first
250             // which should not trigger auto-enter PiP
251             Intent sharingIntent = new Intent(Intent.ACTION_SEND);
252             sharingIntent.setDataAndType(uri, "image/png");
253             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
254             // Include URI in ClipData also, so that grantPermission picks it up.
255             // We don't use setData here because some apps interpret this as "to:".
256             ClipData clipdata = new ClipData(new ClipDescription("content",
257                     new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
258                     new ClipData.Item(uri));
259             sharingIntent.setClipData(clipdata);
260             sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
261             sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
262                     .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
263 
264 
265             // Make sure pending intents for the system user are still unique across users
266             // by setting the (otherwise unused) request code to the current user id.
267             int requestCode = context.getUserId();
268 
269             Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null)
270                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
271                     .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
272 
273 
274             // cancel current pending intent (if any) since clipData isn't used for matching
275             PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
276                     context, 0, sharingChooserIntent,
277                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
278                     transition.bundle, UserHandle.CURRENT);
279 
280             // Create a share action for the notification
281             PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
282                     new Intent(context, ActionProxyReceiver.class)
283                             .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
284                             .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
285                             .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
286                             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
287                                     smartActionsEnabled)
288                             .setAction(Intent.ACTION_SEND)
289                             .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
290                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
291                     UserHandle.SYSTEM);
292 
293             Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
294                     Icon.createWithResource(r, R.drawable.ic_screenshot_share),
295                     r.getString(com.android.internal.R.string.share), shareAction);
296 
297             transition.action = shareActionBuilder.build();
298             return transition;
299         };
300     }
301 
302     @VisibleForTesting
createEditAction(Context context, Resources r, Uri uri, boolean smartActionsEnabled)303     Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
304             boolean smartActionsEnabled) {
305         return () -> {
306             ActionTransition transition = mSharedElementTransition.get();
307             // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
308             // order to do some common work like dismissing the keyguard and sending
309             // closeSystemWindows
310 
311             // Create an edit intent, if a specific package is provided as the editor, then
312             // launch that directly
313             String editorPackage = context.getString(R.string.config_screenshotEditor);
314             Intent editIntent = new Intent(Intent.ACTION_EDIT);
315             if (!TextUtils.isEmpty(editorPackage)) {
316                 editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
317             }
318             editIntent.setDataAndType(uri, "image/png");
319             editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
320             editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
321             editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
322 
323             PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
324                     context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE,
325                     transition.bundle, UserHandle.CURRENT);
326 
327             // Make sure pending intents for the system user are still unique across users
328             // by setting the (otherwise unused) request code to the current user id.
329             int requestCode = mContext.getUserId();
330 
331             // Create an edit action
332             PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
333                     new Intent(context, ActionProxyReceiver.class)
334                             .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
335                             .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
336                             .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
337                                     smartActionsEnabled)
338                             .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
339                             .setAction(Intent.ACTION_EDIT)
340                             .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
341                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
342                     UserHandle.SYSTEM);
343             Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
344                     Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
345                     r.getString(com.android.internal.R.string.screenshot_edit), editAction);
346 
347             transition.action = editActionBuilder.build();
348             return transition;
349         };
350     }
351 
352     @VisibleForTesting
353     Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
354             boolean smartActionsEnabled) {
355         // Make sure pending intents for the system user are still unique across users
356         // by setting the (otherwise unused) request code to the current user id.
357         int requestCode = mContext.getUserId();
358 
359         // Create a delete action for the notification
360         PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
361                 new Intent(context, DeleteScreenshotReceiver.class)
362                         .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
363                         .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
364                         .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
365                                 smartActionsEnabled)
366                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
367                 PendingIntent.FLAG_CANCEL_CURRENT
368                         | PendingIntent.FLAG_ONE_SHOT
369                         | PendingIntent.FLAG_IMMUTABLE);
370         Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
371                 Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
372                 r.getString(com.android.internal.R.string.delete), deleteAction);
373 
374         return deleteActionBuilder.build();
375     }
376 
377     private UserHandle getUserHandleOfForegroundApplication(Context context) {
378         UserManager manager = UserManager.get(context);
379         int result;
380         // This logic matches
381         // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
382         try {
383             result = ActivityTaskManager.getService().getLastResumedActivityUserId();
384         } catch (RemoteException e) {
385             if (DEBUG_ACTIONS) {
386                 Log.d(TAG, "Failed to get UserHandle of foreground app: ", e);
387             }
388             result = context.getUserId();
389         }
390         UserInfo userInfo = manager.getUserInfo(result);
391         return userInfo.getUserHandle();
392     }
393 
394     private List<Notification.Action> buildSmartActions(
395             List<Notification.Action> actions, Context context) {
396         List<Notification.Action> broadcastActions = new ArrayList<>();
397         for (Notification.Action action : actions) {
398             // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
399             Bundle extras = action.getExtras();
400             String actionType = extras.getString(
401                     ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
402                     ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
403             Intent intent = new Intent(context, SmartActionsReceiver.class)
404                     .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
405                     .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
406             addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
407             PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
408                     mRandom.nextInt(),
409                     intent,
410                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
411             broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
412                     broadcastIntent).setContextual(true).addExtras(extras).build());
413         }
414         return broadcastActions;
415     }
416 
417     private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
418             boolean smartActionsEnabled) {
419         intent
420                 .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
421                 .putExtra(ScreenshotController.EXTRA_ID, screenshotId)
422                 .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
423     }
424 
425     /**
426      * Populate image uri into intent of Quick Share action.
427      */
428     @VisibleForTesting
429     private Notification.Action createQuickShareAction(Context context, Notification.Action action,
430             Uri uri) {
431         if (action == null) {
432             return null;
433         }
434         // Populate image URI into Quick Share chip intent
435         Intent sharingIntent = action.actionIntent.getIntent();
436         sharingIntent.setType("image/png");
437         sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
438         String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
439         String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
440         sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
441         // Include URI in ClipData also, so that grantPermission picks it up.
442         // We don't use setData here because some apps interpret this as "to:".
443         ClipData clipdata = new ClipData(new ClipDescription("content",
444                 new String[]{"image/png"}),
445                 new ClipData.Item(uri));
446         sharingIntent.setClipData(clipdata);
447         sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
448         PendingIntent updatedPendingIntent = PendingIntent.getActivity(
449                 context, 0, sharingIntent,
450                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
451 
452         // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
453         Bundle extras = action.getExtras();
454         String actionType = extras.getString(
455                 ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
456                 ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
457         Intent intent = new Intent(context, SmartActionsReceiver.class)
458                 .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
459                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
460         // We only query for quick share actions when smart actions are enabled, so we can assert
461         // that it's true here.
462         addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
463         PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
464                 mRandom.nextInt(),
465                 intent,
466                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
467         return new Notification.Action.Builder(action.getIcon(), action.title,
468                 broadcastIntent).setContextual(true).addExtras(extras).build();
469     }
470 
471     /**
472      * Query and surface Quick Share chip if it is available. Action intent would not be used,
473      * because it does not contain image URL which would be populated in {@link
474      * #createQuickShareAction(Context, Notification.Action, Uri)}
475      */
476     private void queryQuickShareAction(Bitmap image, UserHandle user) {
477         CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
478                 mScreenshotSmartActions.getSmartActionsFuture(
479                         mScreenshotId, null, image, mSmartActionsProvider,
480                         ScreenshotSmartActionType.QUICK_SHARE_ACTION,
481                         true /* smartActionsEnabled */, user);
482         int timeoutMs = DeviceConfig.getInt(
483                 DeviceConfig.NAMESPACE_SYSTEMUI,
484                 SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
485                 500);
486         List<Notification.Action> quickShareActions =
487                 mScreenshotSmartActions.getSmartActions(
488                         mScreenshotId, quickShareActionsFuture, timeoutMs,
489                         mSmartActionsProvider,
490                         ScreenshotSmartActionType.QUICK_SHARE_ACTION);
491         if (!quickShareActions.isEmpty()) {
492             mQuickShareData.quickShareAction = quickShareActions.get(0);
493             mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
494         }
495     }
496 
497     private String getSubjectString() {
498         String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
499         return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
500     }
501 }
502