| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.printing; | 5 package org.chromium.chrome.browser.share; |
| 6 | 6 |
| 7 import android.app.Activity; | 7 import android.app.Activity; |
| 8 import android.content.ComponentName; | 8 import android.content.ComponentName; |
| 9 import android.content.Intent; | |
| 10 import android.content.pm.PackageManager; | 9 import android.content.pm.PackageManager; |
| 11 import android.os.AsyncTask; | 10 import android.os.AsyncTask; |
| 12 import android.os.Bundle; | |
| 13 import android.os.StrictMode; | 11 import android.os.StrictMode; |
| 14 import android.support.v7.app.AppCompatActivity; | |
| 15 | 12 |
| 16 import org.chromium.base.ActivityState; | 13 import org.chromium.base.ActivityState; |
| 17 import org.chromium.base.ApplicationStatus; | 14 import org.chromium.base.ApplicationStatus; |
| 18 import org.chromium.base.ApplicationStatus.ActivityStateListener; | 15 import org.chromium.base.ApplicationStatus.ActivityStateListener; |
| 19 import org.chromium.base.Log; | 16 import org.chromium.base.Log; |
| 20 import org.chromium.base.ThreadUtils; | 17 import org.chromium.base.ThreadUtils; |
| 21 import org.chromium.chrome.R; | |
| 22 import org.chromium.chrome.browser.ChromeActivity; | |
| 23 import org.chromium.chrome.browser.share.ShareHelper; | |
| 24 import org.chromium.chrome.browser.util.IntentUtils; | |
| 25 | 18 |
| 26 import java.lang.ref.WeakReference; | 19 import java.util.ArrayList; |
| 27 import java.util.Collections; | 20 import java.util.Collections; |
| 28 import java.util.HashSet; | 21 import java.util.HashSet; |
| 29 import java.util.List; | 22 import java.util.List; |
| 30 import java.util.Set; | 23 import java.util.Set; |
| 31 import java.util.concurrent.ExecutionException; | 24 import java.util.concurrent.ExecutionException; |
| 32 | 25 |
| 33 /** | 26 /** |
| 34 * A simple activity that allows Chrome to expose print as an option in the shar
e menu. | 27 * A manager for optional share activities in the share picker intent. |
| 35 */ | 28 */ |
| 36 public class PrintShareActivity extends AppCompatActivity { | 29 public class OptionalShareTargetsManager { |
| 37 | 30 private static final String TAG = "share_manager"; |
| 38 private static final String TAG = "cr_printing"; | |
| 39 | 31 |
| 40 private static Set<Activity> sPendingShareActivities = | 32 private static Set<Activity> sPendingShareActivities = |
| 41 Collections.synchronizedSet(new HashSet<Activity>()); | 33 Collections.synchronizedSet(new HashSet<Activity>()); |
| 42 private static ActivityStateListener sStateListener; | 34 private static ActivityStateListener sStateListener; |
| 43 private static AsyncTask<Void, Void, Void> sStateChangeTask; | 35 private static AsyncTask<Void, Void, Void> sStateChangeTask; |
| 36 private static List<ComponentName> sEnabledComponents; |
| 44 | 37 |
| 45 /** | 38 /** |
| 46 * Enable the print sharing option. | 39 * Enables sharing options. |
| 47 * | 40 * @param triggeringActivity The activity that will be triggering the share
action. The |
| 48 * @param activity The activity that will be triggering the share action. T
he activitiy's | 41 * activity's state will be tracked to disable the options w
hen |
| 49 * state will be tracked to disable the print option when th
e share operation | 42 * the share operation has been completed. |
| 50 * has been completed. | 43 * @param enabledClasses classes to be enabled. |
| 51 * @param callback The callback to be triggered after the print option has b
een enabled. This | 44 * @param callback The callback to be triggered after the options have been
enabled. This |
| 52 * may or may not be synchronous depending on whether this w
ill require | 45 * may or may not be synchronous depending on whether this w
ill require |
| 53 * interacting with the Android framework. | 46 * interacting with the Android framework. |
| 54 */ | 47 */ |
| 55 public static void enablePrintShareOption(final Activity activity, final Run
nable callback) { | 48 public static void enableOptionalShareActivities(final Activity triggeringAc
tivity, |
| 49 final List<Class<? extends Activity>> enabledClasses, final Runnable
callback) { |
| 56 ThreadUtils.assertOnUiThread(); | 50 ThreadUtils.assertOnUiThread(); |
| 57 | 51 |
| 58 if (sStateListener == null) { | 52 if (sStateListener == null) { |
| 59 sStateListener = new ActivityStateListener() { | 53 sStateListener = new ActivityStateListener() { |
| 60 @Override | 54 @Override |
| 61 public void onActivityStateChange(Activity activity, int newStat
e) { | 55 public void onActivityStateChange(Activity triggeringActivity, i
nt newState) { |
| 62 if (newState == ActivityState.PAUSED) return; | 56 if (newState == ActivityState.PAUSED) return; |
| 63 unregisterActivity(activity); | 57 handleShareFinish(triggeringActivity); |
| 64 } | 58 } |
| 65 }; | 59 }; |
| 66 } | 60 } |
| 67 ApplicationStatus.registerStateListenerForAllActivities(sStateListener); | 61 ApplicationStatus.registerStateListenerForAllActivities(sStateListener); |
| 68 boolean wasEmpty = sPendingShareActivities.isEmpty(); | 62 boolean wasEmpty = sPendingShareActivities.isEmpty(); |
| 69 sPendingShareActivities.add(activity); | 63 sPendingShareActivities.add(triggeringActivity); |
| 70 | 64 |
| 71 waitForPendingStateChangeTask(); | 65 waitForPendingStateChangeTask(); |
| 72 if (wasEmpty) { | 66 if (wasEmpty) { |
| 67 // Note: possible race condition if two calls to this method happen
simultaneously. |
| 73 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | 68 sStateChangeTask = new AsyncTask<Void, Void, Void>() { |
| 74 @Override | 69 @Override |
| 75 protected Void doInBackground(Void... params) { | 70 protected Void doInBackground(Void... params) { |
| 76 if (sPendingShareActivities.isEmpty()) return null; | 71 if (sPendingShareActivities.isEmpty()) return null; |
| 77 | 72 sEnabledComponents = new ArrayList<>(enabledClasses.size()); |
| 78 activity.getPackageManager().setComponentEnabledSetting( | 73 for (int i = 0; i < enabledClasses.size(); i++) { |
| 79 new ComponentName(activity, PrintShareActivity.class
), | 74 ComponentName newEnabledComponent = |
| 80 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, | 75 new ComponentName(triggeringActivity, enabledCla
sses.get(i)); |
| 81 PackageManager.DONT_KILL_APP); | 76 triggeringActivity.getPackageManager().setComponentEnabl
edSetting( |
| 77 newEnabledComponent, PackageManager.COMPONENT_EN
ABLED_STATE_ENABLED, |
| 78 PackageManager.DONT_KILL_APP); |
| 79 sEnabledComponents.add(newEnabledComponent); |
| 80 } |
| 82 return null; | 81 return null; |
| 83 } | 82 } |
| 84 | 83 |
| 85 @Override | 84 @Override |
| 86 protected void onPostExecute(Void result) { | 85 protected void onPostExecute(Void result) { |
| 87 if (sStateChangeTask == this) { | 86 if (sStateChangeTask == this) { |
| 88 sStateChangeTask = null; | 87 sStateChangeTask = null; |
| 89 } else { | 88 } else { |
| 90 waitForPendingStateChangeTask(); | 89 waitForPendingStateChangeTask(); |
| 91 } | 90 } |
| 92 callback.run(); | 91 callback.run(); |
| 93 } | 92 } |
| 94 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | 93 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 95 } else { | 94 } else { |
| 96 callback.run(); | 95 callback.run(); |
| 97 } | 96 } |
| 98 } | 97 } |
| 99 | 98 |
| 100 private static void unregisterActivity(final Activity activity) { | 99 /** |
| 100 * Handles when the triggering activity has finished the sharing operation.
If all |
| 101 * pending shares have been complete then it will disable all enabled compon
ents. |
| 102 * @param triggeringActivity The activity that is triggering the share actio
n. |
| 103 */ |
| 104 public static void handleShareFinish(final Activity triggeringActivity) { |
| 101 ThreadUtils.assertOnUiThread(); | 105 ThreadUtils.assertOnUiThread(); |
| 102 | 106 |
| 103 sPendingShareActivities.remove(activity); | 107 sPendingShareActivities.remove(triggeringActivity); |
| 104 if (!sPendingShareActivities.isEmpty()) return; | 108 if (!sPendingShareActivities.isEmpty()) return; |
| 105 ApplicationStatus.unregisterActivityStateListener(sStateListener); | 109 ApplicationStatus.unregisterActivityStateListener(sStateListener); |
| 106 | 110 |
| 107 waitForPendingStateChangeTask(); | 111 waitForPendingStateChangeTask(); |
| 108 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | 112 sStateChangeTask = new AsyncTask<Void, Void, Void>() { |
| 109 @Override | 113 @Override |
| 110 protected Void doInBackground(Void... params) { | 114 protected Void doInBackground(Void... params) { |
| 111 if (!sPendingShareActivities.isEmpty()) return null; | 115 if (!sPendingShareActivities.isEmpty()) return null; |
| 112 | 116 for (int i = 0; i < sEnabledComponents.size(); i++) { |
| 113 activity.getPackageManager().setComponentEnabledSetting( | 117 triggeringActivity.getPackageManager().setComponentEnabledSe
tting( |
| 114 new ComponentName(activity, PrintShareActivity.class), | 118 sEnabledComponents.get(i), |
| 115 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, | 119 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, |
| 116 PackageManager.DONT_KILL_APP); | 120 PackageManager.DONT_KILL_APP); |
| 121 } |
| 117 return null; | 122 return null; |
| 118 } | 123 } |
| 119 | 124 |
| 120 @Override | 125 @Override |
| 121 protected void onPostExecute(Void result) { | 126 protected void onPostExecute(Void result) { |
| 122 if (sStateChangeTask == this) sStateChangeTask = null; | 127 if (sStateChangeTask == this) sStateChangeTask = null; |
| 123 } | 128 } |
| 124 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | 129 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| 125 } | 130 } |
| 126 | 131 |
| 127 /** | 132 /** |
| 128 * Waits for any pending state change operations to be completed. | 133 * Waits for any pending state change operations to be completed. |
| 129 * | 134 * |
| 130 * This will avoid timing issues described here: crbug.com/649453. | 135 * This will avoid timing issues described here: crbug.com/649453. |
| 131 */ | 136 */ |
| 132 private static void waitForPendingStateChangeTask() { | 137 private static void waitForPendingStateChangeTask() { |
| 133 ThreadUtils.assertOnUiThread(); | 138 ThreadUtils.assertOnUiThread(); |
| 134 | 139 |
| 135 if (sStateChangeTask == null) return; | 140 if (sStateChangeTask == null) return; |
| 136 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | 141 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
| 137 try { | 142 try { |
| 138 sStateChangeTask.get(); | 143 sStateChangeTask.get(); |
| 139 sStateChangeTask = null; | 144 sStateChangeTask = null; |
| 140 } catch (InterruptedException | ExecutionException e) { | 145 } catch (InterruptedException | ExecutionException e) { |
| 141 Log.e(TAG, "Print state change task did not complete as expected"); | 146 Log.e(TAG, "State change task did not complete as expected"); |
| 142 } finally { | 147 } finally { |
| 143 StrictMode.setThreadPolicy(oldPolicy); | 148 StrictMode.setThreadPolicy(oldPolicy); |
| 144 } | 149 } |
| 145 } | 150 } |
| 146 | 151 } |
| 147 @Override | |
| 148 protected void onCreate(Bundle savedInstanceState) { | |
| 149 super.onCreate(savedInstanceState); | |
| 150 | |
| 151 try { | |
| 152 Intent intent = getIntent(); | |
| 153 if (intent == null) return; | |
| 154 if (!Intent.ACTION_SEND.equals(intent.getAction())) return; | |
| 155 if (!IntentUtils.safeHasExtra(getIntent(), ShareHelper.EXTRA_TASK_ID
)) return; | |
| 156 handlePrintAction(); | |
| 157 } finally { | |
| 158 finish(); | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 private void handlePrintAction() { | |
| 163 int triggeringTaskId = | |
| 164 IntentUtils.safeGetIntExtra(getIntent(), ShareHelper.EXTRA_TASK_
ID, 0); | |
| 165 List<WeakReference<Activity>> activities = ApplicationStatus.getRunningA
ctivities(); | |
| 166 ChromeActivity triggeringActivity = null; | |
| 167 for (int i = 0; i < activities.size(); i++) { | |
| 168 Activity activity = activities.get(i).get(); | |
| 169 if (activity == null) continue; | |
| 170 | |
| 171 // Since the share intent is triggered without NEW_TASK or NEW_DOCUM
ENT, the task ID | |
| 172 // of this activity will match that of the triggering activity. | |
| 173 if (activity.getTaskId() == triggeringTaskId | |
| 174 && activity instanceof ChromeActivity) { | |
| 175 triggeringActivity = (ChromeActivity) activity; | |
| 176 break; | |
| 177 } | |
| 178 } | |
| 179 if (triggeringActivity == null) return; | |
| 180 unregisterActivity(triggeringActivity); | |
| 181 triggeringActivity.onMenuOrKeyboardAction(R.id.print_id, true); | |
| 182 } | |
| 183 | |
| 184 } | |
| OLD | NEW |