| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 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.printing; |
| 6 | 6 |
| 7 import android.app.Activity; | |
| 8 import android.content.ComponentName; | |
| 9 import android.content.Intent; | |
| 10 import android.content.pm.PackageManager; | |
| 11 import android.os.AsyncTask; | |
| 12 import android.os.Bundle; | |
| 13 import android.os.StrictMode; | |
| 14 import android.support.v7.app.AppCompatActivity; | |
| 15 | |
| 16 import org.chromium.base.ActivityState; | |
| 17 import org.chromium.base.ApplicationStatus; | |
| 18 import org.chromium.base.ApplicationStatus.ActivityStateListener; | |
| 19 import org.chromium.base.Log; | |
| 20 import org.chromium.base.ThreadUtils; | |
| 21 import org.chromium.chrome.R; | 7 import org.chromium.chrome.R; |
| 22 import org.chromium.chrome.browser.ChromeActivity; | 8 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 23 import org.chromium.chrome.browser.share.ShareHelper; | 9 import org.chromium.chrome.browser.share.ShareActivity; |
| 24 import org.chromium.chrome.browser.util.IntentUtils; | 10 import org.chromium.chrome.browser.tab.Tab; |
| 25 | 11 import org.chromium.printing.PrintingController; |
| 26 import java.lang.ref.WeakReference; | 12 import org.chromium.printing.PrintingControllerImpl; |
| 27 import java.util.Collections; | |
| 28 import java.util.HashSet; | |
| 29 import java.util.List; | |
| 30 import java.util.Set; | |
| 31 import java.util.concurrent.ExecutionException; | |
| 32 | 13 |
| 33 /** | 14 /** |
| 34 * A simple activity that allows Chrome to expose print as an option in the shar
e menu. | 15 * A simple activity that allows Chrome to expose print as an option in the shar
e menu. |
| 35 */ | 16 */ |
| 36 public class PrintShareActivity extends AppCompatActivity { | 17 public class PrintShareActivity extends ShareActivity { |
| 37 | 18 |
| 38 private static final String TAG = "cr_printing"; | 19 private static final String TAG = "cr_printing"; |
| 39 | 20 |
| 40 private static Set<Activity> sPendingShareActivities = | 21 @Override |
| 41 Collections.synchronizedSet(new HashSet<Activity>()); | 22 protected void handleShareAction() { |
| 42 private static ActivityStateListener sStateListener; | 23 getTriggeringActivity().onMenuOrKeyboardAction(R.id.print_id, true); |
| 43 private static AsyncTask<Void, Void, Void> sStateChangeTask; | |
| 44 | |
| 45 /** | |
| 46 * Enable the print sharing option. | |
| 47 * | |
| 48 * @param activity The activity that will be triggering the share action. T
he activitiy's | |
| 49 * state will be tracked to disable the print option when th
e share operation | |
| 50 * has been completed. | |
| 51 * @param callback The callback to be triggered after the print option has b
een enabled. This | |
| 52 * may or may not be synchronous depending on whether this w
ill require | |
| 53 * interacting with the Android framework. | |
| 54 */ | |
| 55 public static void enablePrintShareOption(final Activity activity, final Run
nable callback) { | |
| 56 ThreadUtils.assertOnUiThread(); | |
| 57 | |
| 58 if (sStateListener == null) { | |
| 59 sStateListener = new ActivityStateListener() { | |
| 60 @Override | |
| 61 public void onActivityStateChange(Activity activity, int newStat
e) { | |
| 62 if (newState == ActivityState.PAUSED) return; | |
| 63 unregisterActivity(activity); | |
| 64 } | |
| 65 }; | |
| 66 } | |
| 67 ApplicationStatus.registerStateListenerForAllActivities(sStateListener); | |
| 68 boolean wasEmpty = sPendingShareActivities.isEmpty(); | |
| 69 sPendingShareActivities.add(activity); | |
| 70 | |
| 71 waitForPendingStateChangeTask(); | |
| 72 if (wasEmpty) { | |
| 73 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | |
| 74 @Override | |
| 75 protected Void doInBackground(Void... params) { | |
| 76 if (sPendingShareActivities.isEmpty()) return null; | |
| 77 | |
| 78 activity.getPackageManager().setComponentEnabledSetting( | |
| 79 new ComponentName(activity, PrintShareActivity.class
), | |
| 80 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, | |
| 81 PackageManager.DONT_KILL_APP); | |
| 82 return null; | |
| 83 } | |
| 84 | |
| 85 @Override | |
| 86 protected void onPostExecute(Void result) { | |
| 87 if (sStateChangeTask == this) { | |
| 88 sStateChangeTask = null; | |
| 89 } else { | |
| 90 waitForPendingStateChangeTask(); | |
| 91 } | |
| 92 callback.run(); | |
| 93 } | |
| 94 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| 95 } else { | |
| 96 callback.run(); | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 private static void unregisterActivity(final Activity activity) { | |
| 101 ThreadUtils.assertOnUiThread(); | |
| 102 | |
| 103 sPendingShareActivities.remove(activity); | |
| 104 if (!sPendingShareActivities.isEmpty()) return; | |
| 105 ApplicationStatus.unregisterActivityStateListener(sStateListener); | |
| 106 | |
| 107 waitForPendingStateChangeTask(); | |
| 108 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | |
| 109 @Override | |
| 110 protected Void doInBackground(Void... params) { | |
| 111 if (!sPendingShareActivities.isEmpty()) return null; | |
| 112 | |
| 113 activity.getPackageManager().setComponentEnabledSetting( | |
| 114 new ComponentName(activity, PrintShareActivity.class), | |
| 115 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, | |
| 116 PackageManager.DONT_KILL_APP); | |
| 117 return null; | |
| 118 } | |
| 119 | |
| 120 @Override | |
| 121 protected void onPostExecute(Void result) { | |
| 122 if (sStateChangeTask == this) sStateChangeTask = null; | |
| 123 } | |
| 124 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| 125 } | |
| 126 | |
| 127 /** | |
| 128 * Waits for any pending state change operations to be completed. | |
| 129 * | |
| 130 * This will avoid timing issues described here: crbug.com/649453. | |
| 131 */ | |
| 132 private static void waitForPendingStateChangeTask() { | |
| 133 ThreadUtils.assertOnUiThread(); | |
| 134 | |
| 135 if (sStateChangeTask == null) return; | |
| 136 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | |
| 137 try { | |
| 138 sStateChangeTask.get(); | |
| 139 sStateChangeTask = null; | |
| 140 } catch (InterruptedException | ExecutionException e) { | |
| 141 Log.e(TAG, "Print state change task did not complete as expected"); | |
| 142 } finally { | |
| 143 StrictMode.setThreadPolicy(oldPolicy); | |
| 144 } | |
| 145 } | 24 } |
| 146 | 25 |
| 147 @Override | 26 @Override |
| 148 protected void onCreate(Bundle savedInstanceState) { | 27 public boolean featureIsEnabled(Tab currentTab) { |
| 149 super.onCreate(savedInstanceState); | 28 PrintingController printingController = PrintingControllerImpl.getInstan
ce(); |
| 150 | 29 if (printingController != null && !currentTab.isNativePage() |
| 151 try { | 30 && !printingController.isBusy() |
| 152 Intent intent = getIntent(); | 31 && PrefServiceBridge.getInstance().isPrintingEnabled()) { |
| 153 if (intent == null) return; | 32 return true; |
| 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 } | 33 } |
| 34 return false; |
| 160 } | 35 } |
| 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 } | 36 } |
| OLD | NEW |