Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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.share; | 5 package org.chromium.chrome.browser.share; |
| 6 | 6 |
| 7 import android.annotation.TargetApi; | 7 import android.annotation.TargetApi; |
| 8 import android.app.Activity; | 8 import android.app.Activity; |
| 9 import android.app.PendingIntent; | 9 import android.app.PendingIntent; |
| 10 import android.content.BroadcastReceiver; | 10 import android.content.BroadcastReceiver; |
| 11 import android.content.ClipData; | 11 import android.content.ClipData; |
| 12 import android.content.ComponentName; | 12 import android.content.ComponentName; |
| 13 import android.content.Context; | 13 import android.content.Context; |
| 14 import android.content.DialogInterface; | 14 import android.content.DialogInterface; |
| 15 import android.content.DialogInterface.OnDismissListener; | 15 import android.content.DialogInterface.OnDismissListener; |
| 16 import android.content.Intent; | 16 import android.content.Intent; |
| 17 import android.content.IntentFilter; | 17 import android.content.IntentFilter; |
| 18 import android.content.SharedPreferences; | 18 import android.content.SharedPreferences; |
| 19 import android.content.pm.ActivityInfo; | 19 import android.content.pm.ActivityInfo; |
| 20 import android.content.pm.ApplicationInfo; | 20 import android.content.pm.ApplicationInfo; |
| 21 import android.content.pm.PackageManager; | 21 import android.content.pm.PackageManager; |
| 22 import android.content.pm.PackageManager.NameNotFoundException; | 22 import android.content.pm.PackageManager.NameNotFoundException; |
| 23 import android.content.pm.ResolveInfo; | 23 import android.content.pm.ResolveInfo; |
| 24 import android.graphics.Bitmap; | 24 import android.graphics.Bitmap; |
| 25 import android.graphics.drawable.Drawable; | 25 import android.graphics.drawable.Drawable; |
| 26 import android.net.Uri; | 26 import android.net.Uri; |
| 27 import android.os.AsyncTask; | 27 import android.os.AsyncTask; |
| 28 import android.os.Build; | 28 import android.os.Build; |
| 29 import android.os.StrictMode; | |
| 29 import android.support.annotation.Nullable; | 30 import android.support.annotation.Nullable; |
| 30 import android.support.v7.app.AlertDialog; | 31 import android.support.v7.app.AlertDialog; |
| 31 import android.text.TextUtils; | 32 import android.text.TextUtils; |
| 32 import android.util.Pair; | 33 import android.util.Pair; |
| 33 import android.view.MenuItem; | 34 import android.view.MenuItem; |
| 34 import android.view.View; | 35 import android.view.View; |
| 35 import android.widget.AdapterView; | 36 import android.widget.AdapterView; |
| 36 import android.widget.AdapterView.OnItemClickListener; | 37 import android.widget.AdapterView.OnItemClickListener; |
| 37 | 38 |
| 39 import org.chromium.base.ActivityState; | |
| 38 import org.chromium.base.ApiCompatibilityUtils; | 40 import org.chromium.base.ApiCompatibilityUtils; |
| 39 import org.chromium.base.ApplicationState; | 41 import org.chromium.base.ApplicationState; |
| 40 import org.chromium.base.ApplicationStatus; | 42 import org.chromium.base.ApplicationStatus; |
| 43 import org.chromium.base.ApplicationStatus.ActivityStateListener; | |
| 41 import org.chromium.base.Callback; | 44 import org.chromium.base.Callback; |
| 42 import org.chromium.base.ContextUtils; | 45 import org.chromium.base.ContextUtils; |
| 43 import org.chromium.base.Log; | 46 import org.chromium.base.Log; |
| 44 import org.chromium.base.StreamUtil; | 47 import org.chromium.base.StreamUtil; |
| 48 import org.chromium.base.ThreadUtils; | |
| 45 import org.chromium.base.VisibleForTesting; | 49 import org.chromium.base.VisibleForTesting; |
| 46 import org.chromium.base.annotations.SuppressFBWarnings; | 50 import org.chromium.base.annotations.SuppressFBWarnings; |
| 47 import org.chromium.base.metrics.RecordHistogram; | 51 import org.chromium.base.metrics.RecordHistogram; |
| 48 import org.chromium.chrome.R; | 52 import org.chromium.chrome.R; |
| 49 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; | 53 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| 50 import org.chromium.ui.UiUtils; | 54 import org.chromium.ui.UiUtils; |
| 51 | 55 |
| 52 import java.io.File; | 56 import java.io.File; |
| 53 import java.io.FileOutputStream; | 57 import java.io.FileOutputStream; |
| 54 import java.io.IOException; | 58 import java.io.IOException; |
| 55 import java.util.Collections; | 59 import java.util.Collections; |
| 60 import java.util.HashSet; | |
| 56 import java.util.List; | 61 import java.util.List; |
| 62 import java.util.Set; | |
| 57 import java.util.concurrent.ExecutionException; | 63 import java.util.concurrent.ExecutionException; |
| 58 import java.util.concurrent.TimeUnit; | 64 import java.util.concurrent.TimeUnit; |
| 59 import java.util.concurrent.TimeoutException; | 65 import java.util.concurrent.TimeoutException; |
| 60 | 66 |
| 61 /** | 67 /** |
| 62 * A helper class that helps to start an intent to share titles and URLs. | 68 * A helper class that helps to start an intent to share titles and URLs. |
| 63 */ | 69 */ |
| 64 public class ShareHelper { | 70 public class ShareHelper { |
| 65 | 71 |
| 66 /** Interface that receives intents for testing (to fake out actually sendin g them). */ | 72 /** Interface that receives intents for testing (to fake out actually sendin g them). */ |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 97 * Named "screenshot" for historical reasons as we only initially shared scr eenshot images. | 103 * Named "screenshot" for historical reasons as we only initially shared scr eenshot images. |
| 98 */ | 104 */ |
| 99 private static final String SHARE_IMAGES_DIRECTORY_NAME = "screenshot"; | 105 private static final String SHARE_IMAGES_DIRECTORY_NAME = "screenshot"; |
| 100 | 106 |
| 101 /** Force the use of a Chrome-specific intent chooser, not the system choose r. */ | 107 /** Force the use of a Chrome-specific intent chooser, not the system choose r. */ |
| 102 private static boolean sForceCustomChooserForTesting; | 108 private static boolean sForceCustomChooserForTesting; |
| 103 | 109 |
| 104 /** If non-null, will be used instead of the real activity. */ | 110 /** If non-null, will be used instead of the real activity. */ |
| 105 private static FakeIntentReceiver sFakeIntentReceiverForTesting; | 111 private static FakeIntentReceiver sFakeIntentReceiverForTesting; |
| 106 | 112 |
| 113 /** Variables for adding options dynamically to app chooser. */ | |
| 114 private static Set<Activity> sPendingShareActivities = | |
| 115 Collections.synchronizedSet(new HashSet<Activity>()); | |
| 116 private static ActivityStateListener sStateListener; | |
| 117 private static AsyncTask<Void, Void, Void> sStateChangeTask; | |
| 118 | |
| 107 private ShareHelper() {} | 119 private ShareHelper() {} |
| 108 | 120 |
| 109 private static void fireIntent(Activity activity, Intent intent) { | 121 private static void fireIntent(Activity activity, Intent intent) { |
| 110 if (sFakeIntentReceiverForTesting != null) { | 122 if (sFakeIntentReceiverForTesting != null) { |
| 111 Context context = activity.getApplicationContext(); | 123 Context context = activity.getApplicationContext(); |
| 112 sFakeIntentReceiverForTesting.fireIntent(context, intent); | 124 sFakeIntentReceiverForTesting.fireIntent(context, intent); |
| 113 } else { | 125 } else { |
| 114 activity.startActivity(intent); | 126 activity.startActivity(intent); |
| 115 } | 127 } |
| 116 } | 128 } |
| (...skipping 557 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 674 return intent; | 686 return intent; |
| 675 } | 687 } |
| 676 | 688 |
| 677 private static ComponentName getLastShareComponentName() { | 689 private static ComponentName getLastShareComponentName() { |
| 678 SharedPreferences preferences = ContextUtils.getAppSharedPreferences(); | 690 SharedPreferences preferences = ContextUtils.getAppSharedPreferences(); |
| 679 String packageName = preferences.getString(PACKAGE_NAME_KEY, null); | 691 String packageName = preferences.getString(PACKAGE_NAME_KEY, null); |
| 680 String className = preferences.getString(CLASS_NAME_KEY, null); | 692 String className = preferences.getString(CLASS_NAME_KEY, null); |
| 681 if (packageName == null || className == null) return null; | 693 if (packageName == null || className == null) return null; |
| 682 return new ComponentName(packageName, className); | 694 return new ComponentName(packageName, className); |
| 683 } | 695 } |
| 696 | |
| 697 /** | |
| 698 * Dynamically add Chrome activities to Share chooser. | |
| 699 * | |
| 700 * @param activity The activity that will be triggering the share action. | |
| 701 * @param options List of classes to add to the chooser. | |
| 702 * @param callback The callback to be triggered after the options have been enabled. This | |
| 703 * may or may not be synchronous depending on whether this w ill require | |
| 704 * interacting with the Android framework. | |
| 705 */ | |
| 706 public static void enableOptions(final Activity activity, | |
| 707 final List<Class> options, final Runnable callback) { | |
| 708 Log.v(TAG, "enableOptions Called"); | |
| 709 ThreadUtils.assertOnUiThread(); | |
| 710 | |
| 711 if (sStateListener == null) { | |
| 712 sStateListener = new ActivityStateListener() { | |
| 713 @Override | |
| 714 public void onActivityStateChange(Activity activity, int newStat e) { | |
| 715 if (newState == ActivityState.PAUSED) return; | |
| 716 unregisterActivity(activity, options); | |
| 717 } | |
| 718 }; | |
| 719 } | |
| 720 ApplicationStatus.registerStateListenerForAllActivities(sStateListener); | |
| 721 boolean wasEmpty = sPendingShareActivities.isEmpty(); | |
| 722 sPendingShareActivities.add(activity); | |
| 723 | |
| 724 waitForPendingStateChangeTask(); | |
| 725 if (wasEmpty) { | |
| 726 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | |
| 727 @Override | |
| 728 protected Void doInBackground(Void... params) { | |
| 729 if (sPendingShareActivities.isEmpty()) return null; | |
| 730 | |
| 731 for (int i = 0; i < options.size(); i++) { | |
| 732 Log.v(TAG, "enableOptions: " + options.get(i).getName()) ; | |
|
cco3
2017/02/02 00:58:58
remove extra logging.
iankc
2017/02/02 01:52:24
Done.
| |
| 733 activity.getPackageManager().setComponentEnabledSetting( | |
| 734 new ComponentName(activity, options.get(i)), | |
| 735 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, | |
| 736 PackageManager.DONT_KILL_APP); | |
| 737 } | |
| 738 return null; | |
| 739 } | |
| 740 | |
| 741 @Override | |
| 742 protected void onPostExecute(Void result) { | |
| 743 if (sStateChangeTask == this) { | |
| 744 sStateChangeTask = null; | |
| 745 } else { | |
| 746 waitForPendingStateChangeTask(); | |
| 747 } | |
| 748 callback.run(); | |
| 749 } | |
| 750 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| 751 } else { | |
| 752 callback.run(); | |
| 753 } | |
| 754 } | |
| 755 | |
| 756 public static void unregisterActivity(final Activity activity, | |
| 757 final List<Class> options) { | |
| 758 Log.v(TAG, "unregisterActivity Called"); | |
| 759 ThreadUtils.assertOnUiThread(); | |
| 760 | |
| 761 sPendingShareActivities.remove(activity); | |
| 762 if (!sPendingShareActivities.isEmpty()) return; | |
| 763 ApplicationStatus.unregisterActivityStateListener(sStateListener); | |
| 764 | |
| 765 waitForPendingStateChangeTask(); | |
| 766 sStateChangeTask = new AsyncTask<Void, Void, Void>() { | |
| 767 @Override | |
| 768 protected Void doInBackground(Void... params) { | |
| 769 if (!sPendingShareActivities.isEmpty()) return null; | |
| 770 | |
| 771 for (int i = 0; i < options.size(); i++) { | |
| 772 Log.v(TAG, "unregisterActivity: " + options.get(i).getNa me()); | |
| 773 activity.getPackageManager().setComponentEnabledSetting( | |
| 774 new ComponentName(activity, options.get(i)), | |
| 775 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, | |
| 776 PackageManager.DONT_KILL_APP); | |
| 777 } | |
| 778 return null; | |
| 779 } | |
| 780 | |
| 781 @Override | |
| 782 protected void onPostExecute(Void result) { | |
| 783 if (sStateChangeTask == this) sStateChangeTask = null; | |
| 784 } | |
| 785 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
| 786 } | |
| 787 | |
| 788 /** | |
| 789 * Waits for any pending state change operations to be completed. | |
| 790 * | |
| 791 * This will avoid timing issues described here: crbug.com/649453. | |
| 792 */ | |
| 793 public static void waitForPendingStateChangeTask() { | |
| 794 ThreadUtils.assertOnUiThread(); | |
| 795 | |
| 796 if (sStateChangeTask == null) return; | |
| 797 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | |
| 798 try { | |
| 799 sStateChangeTask.get(); | |
| 800 sStateChangeTask = null; | |
| 801 } catch (InterruptedException | ExecutionException e) { | |
| 802 Log.e(TAG, "Share Helper state change task did not complete as expec ted"); | |
| 803 } finally { | |
| 804 StrictMode.setThreadPolicy(oldPolicy); | |
| 805 } | |
| 806 } | |
| 684 } | 807 } |
| OLD | NEW |