Commit 0bf3d71d authored by Peter E Conn's avatar Peter E Conn Committed by Chromium LUCI CQ

🍱 Remove ChromeActivity dependence from PictureInPictureController.

Change-Id: I4bbba28ef64ee3c1b989f43a0878d3f14bd33dfe
Bug: 1155373
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2580883Reviewed-by: default avatarBrandon Wylie <wylieb@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Commit-Queue: Peter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835629}
parent a9b0a2bc
......@@ -213,9 +213,6 @@ specific_include_rules = {
"LanguageAskPrompt\.java": [
"+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
],
"PictureInPictureController\.java": [
"+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
],
"ChromeTabModalPresenter\.java": [
"+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
],
......
......@@ -939,7 +939,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
MultiWindowUtils.getInstance().isInMultiWindowMode(this));
if (mPictureInPictureController != null) {
mPictureInPictureController.cleanup(this);
mPictureInPictureController.cleanup();
}
VrModuleProvider.getDelegate().maybeRegisterVrEntryHook(this);
......@@ -954,9 +954,11 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
if (isActivityFinishingOrDestroyed()) return;
if (mPictureInPictureController == null) {
mPictureInPictureController = new PictureInPictureController();
mPictureInPictureController = new PictureInPictureController(
this, getActivityTabProvider(), getFullscreenManager());
}
mPictureInPictureController.attemptPictureInPicture(this);
mPictureInPictureController.attemptPictureInPicture();
}
@Override
......@@ -1000,7 +1002,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
@Override
public void onNewIntentWithNative(Intent intent) {
if (mPictureInPictureController != null) {
mPictureInPictureController.cleanup(this);
mPictureInPictureController.cleanup();
}
super.onNewIntentWithNative(intent);
......
......@@ -7,7 +7,6 @@ package org.chromium.chrome.browser.media;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Build;
......@@ -17,26 +16,21 @@ import android.util.Rational;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.Log;
import org.chromium.base.MathUtils;
import org.chromium.base.annotations.VerifiesOnO;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;
......@@ -57,12 +51,12 @@ public class PictureInPictureController {
private static final int METRICS_ATTEMPT_RESULT_SUCCESS = 0;
private static final int METRICS_ATTEMPT_RESULT_NO_SYSTEM_SUPPORT = 1;
private static final int METRICS_ATTEMPT_RESULT_NO_FEATURE = 2;
private static final int METRICS_ATTEMPT_RESULT_NO_ACTIVITY_SUPPORT = 3;
// Obsolete: private static final int METRICS_ATTEMPT_RESULT_NO_ACTIVITY_SUPPORT = 3;
private static final int METRICS_ATTEMPT_RESULT_ALREADY_RUNNING = 4;
private static final int METRICS_ATTEMPT_RESULT_RESTARTING = 5;
private static final int METRICS_ATTEMPT_RESULT_FINISHING = 6;
private static final int METRICS_ATTEMPT_RESULT_NO_WEBCONTENTS = 7;
// private static final int METRICS_ATTEMPT_RESULT_NO_VIDEO = 8;
private static final int METRICS_ATTEMPT_RESULT_NO_WEB_CONTENTS = 7;
// Obsolete: private static final int METRICS_ATTEMPT_RESULT_NO_VIDEO = 8;
private static final int METRICS_ATTEMPT_RESULT_COUNT = 9;
private static final String METRICS_END_REASON = "Media.VideoPersistence.EndReason";
......@@ -80,18 +74,26 @@ public class PictureInPictureController {
private static final float MAX_ASPECT_RATIO = 2.39f;
/** Callbacks to cleanup after leaving PiP. */
private List<Callback<ChromeActivity>> mOnLeavePipCallbacks = new LinkedList<>();
private final List<Runnable> mOnLeavePipCallbacks = new LinkedList<>();
private final Activity mActivity;
private final ActivityTabProvider mActivityTabProvider;
private final FullscreenManager mFullscreenManager;
public PictureInPictureController(Activity activity, ActivityTabProvider activityTabProvider,
FullscreenManager fullscreenManager) {
mActivity = activity;
mActivityTabProvider = activityTabProvider;
mFullscreenManager = fullscreenManager;
}
/**
* Convenience method to get the {@link WebContents} from the active Tab of a
* {@link ChromeActivity}.
* Convenience method to get the {@link WebContents} from the active Tab.
*/
@Nullable
private static WebContents getWebContents(ChromeActivity activity) {
if (!activity.areTabModelsInitialized()) return null;
assert activity.getTabModelSelector() != null;
if (activity.getActivityTab() == null) return null;
return activity.getActivityTab().getWebContents();
private WebContents getWebContents() {
Tab tab = mActivityTabProvider.get();
if (tab == null) return null;
return tab.getWebContents();
}
private static void recordAttemptResult(int result) {
......@@ -99,9 +101,12 @@ public class PictureInPictureController {
METRICS_ATTEMPT_RESULT, result, METRICS_ATTEMPT_RESULT_COUNT);
}
private boolean shouldAttempt(ChromeActivity activity) {
WebContents webContents = getWebContents(activity);
if (webContents == null) return false;
private boolean shouldAttempt() {
WebContents webContents = getWebContents();
if (webContents == null) {
recordAttemptResult(METRICS_ATTEMPT_RESULT_NO_WEB_CONTENTS);
return false;
}
// Non-null WebContents implies the native library has been loaded.
assert LibraryLoader.getInstance().isInitialized();
......@@ -117,47 +122,29 @@ public class PictureInPictureController {
return false;
}
if (!activity.getPackageManager().hasSystemFeature(
if (!mActivity.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
Log.d(TAG, "Activity does not have PiP feature.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_NO_FEATURE);
return false;
}
// TODO(peconn): Clean this up when we build with the O SDK.
try {
ActivityInfo info =
activity.getPackageManager().getActivityInfo(activity.getComponentName(), 0);
Boolean supports =
(Boolean) info.getClass().getMethod("supportsPictureInPicture").invoke(info);
if (!supports) {
Log.d(TAG, "Activity does not support PiP.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_NO_ACTIVITY_SUPPORT);
return false;
}
} catch (PackageManager.NameNotFoundException | IllegalAccessException
| NoSuchMethodException | InvocationTargetException e) {
Log.d(TAG, "Exception checking whether Activity supports PiP.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_NO_ACTIVITY_SUPPORT);
return false;
}
// Don't PiP if we are already in PiP.
if (activity.isInPictureInPictureMode()) {
if (mActivity.isInPictureInPictureMode()) {
Log.d(TAG, "Activity is already in PiP.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_ALREADY_RUNNING);
return false;
}
// This means the activity is going to be restarted, so don't PiP.
if (activity.isChangingConfigurations()) {
if (mActivity.isChangingConfigurations()) {
Log.d(TAG, "Activity is being restarted.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_RESTARTING);
return false;
}
// Don't PiP if the activity is finishing.
if (activity.isFinishing()) {
if (mActivity.isFinishing()) {
Log.d(TAG, "Activity is finishing.");
recordAttemptResult(METRICS_ATTEMPT_RESULT_FINISHING);
return false;
......@@ -170,14 +157,14 @@ public class PictureInPictureController {
/**
* Attempt to enter Picture in Picture mode if there is fullscreen video.
*/
public void attemptPictureInPicture(final ChromeActivity activity) {
if (!shouldAttempt(activity)) return;
public void attemptPictureInPicture() {
if (!shouldAttempt()) return;
// Inform the WebContents when we enter and when we leave PiP.
final WebContents webContents = getWebContents(activity);
final WebContents webContents = getWebContents();
assert webContents != null;
Rect bounds = getVideoBounds(webContents, activity);
Rect bounds = getVideoBounds(webContents, mActivity);
PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
if (bounds != null) {
builder.setAspectRatio(new Rational(bounds.width(), bounds.height()));
......@@ -185,7 +172,7 @@ public class PictureInPictureController {
}
try {
if (!activity.enterPictureInPictureMode(builder.build())) return;
if (!mActivity.enterPictureInPictureMode(builder.build())) return;
} catch (IllegalStateException | IllegalArgumentException e) {
Log.e(TAG, "Error entering PiP with bounds (%d, %d): %s",
bounds.width(), bounds.height(), e);
......@@ -194,57 +181,46 @@ public class PictureInPictureController {
webContents.setHasPersistentVideo(true);
// We don't want InfoBars displaying while in PiP, they cover too much content.
InfoBarContainer.get(activity.getActivityTab()).setHidden(true);
// Setup observers to dismiss the Activity on events that should end PiP.
final Tab activityTab = activity.getActivityTab();
final Tab activityTab = mActivityTabProvider.get();
mOnLeavePipCallbacks.add(new Callback<ChromeActivity>() {
@Override
public void onResult(ChromeActivity activity2) {
webContents.setHasPersistentVideo(false);
InfoBarContainer.get(activityTab).setHidden(false);
}
// We don't want InfoBars displaying while in PiP, they cover too much content.
InfoBarContainer.get(activityTab).setHidden(true);
mOnLeavePipCallbacks.add(() -> {
webContents.setHasPersistentVideo(false);
InfoBarContainer.get(activityTab).setHidden(false);
});
final TabObserver tabObserver = new DismissActivityOnTabEventObserver(activity);
final TabModelSelectorObserver tabModelSelectorObserver =
new DismissActivityOnTabModelSelectorEventObserver(activity);
final WebContentsObserver webContentsObserver =
new DismissActivityOnWebContentsObserver(activity);
final TabModelSelector tabModelSelector = activity.getTabModelSelector();
final FullscreenManager.Observer fullscreenListener = new FullscreenManager.Observer() {
TabObserver tabObserver = new DismissActivityOnTabEventObserver(mActivity);
ActivityTabProvider.ActivityTabObserver activityTabObserver =
new DismissActivityOnTabChangeObserver(mActivity, activityTab);
WebContentsObserver webContentsObserver =
new DismissActivityOnWebContentsObserver(mActivity);
FullscreenManager.Observer fullscreenListener = new FullscreenManager.Observer() {
@Override
public void onExitFullscreen(Tab tab) {
dismissActivity(activity, METRICS_END_REASON_LEFT_FULLSCREEN);
dismissActivity(mActivity, METRICS_END_REASON_LEFT_FULLSCREEN);
}
};
FullscreenManager fullscreenManager = activity.getFullscreenManager();
fullscreenManager.addObserver(fullscreenListener);
activityTab.addObserver(tabObserver);
tabModelSelector.addObserver(tabModelSelectorObserver);
webContents.addObserver(webContentsObserver);
mOnLeavePipCallbacks.add(new Callback<ChromeActivity>() {
@Override
public void onResult(ChromeActivity activity2) {
activityTab.removeObserver(tabObserver);
tabModelSelector.removeObserver(tabModelSelectorObserver);
webContents.removeObserver(webContentsObserver);
fullscreenManager.removeObserver(fullscreenListener);
}
mFullscreenManager.addObserver(fullscreenListener);
mActivityTabProvider.addObserverAndTrigger(activityTabObserver);
mOnLeavePipCallbacks.add(() -> {
activityTab.removeObserver(tabObserver);
webContents.removeObserver(webContentsObserver);
mFullscreenManager.removeObserver(fullscreenListener);
mActivityTabProvider.removeObserver(activityTabObserver);
});
final long startTimeMs = SystemClock.elapsedRealtime();
mOnLeavePipCallbacks.add(new Callback<ChromeActivity>() {
@Override
public void onResult(ChromeActivity activity2) {
long pipTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
RecordHistogram.recordCustomTimesHistogram(METRICS_DURATION, pipTimeMs,
DateUtils.SECOND_IN_MILLIS * 7, DateUtils.HOUR_IN_MILLIS * 10, 50);
}
long startTimeMs = SystemClock.elapsedRealtime();
mOnLeavePipCallbacks.add(() -> {
long pipTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
RecordHistogram.recordCustomTimesHistogram(METRICS_DURATION, pipTimeMs,
DateUtils.SECOND_IN_MILLIS * 7, DateUtils.HOUR_IN_MILLIS * 10, 50);
});
}
......@@ -289,11 +265,11 @@ public class PictureInPictureController {
/**
* If we have previously entered Picture in Picture, perform cleanup.
*/
public void cleanup(ChromeActivity activity) {
cleanup(activity, METRICS_END_REASON_RESUME);
public void cleanup() {
cleanup(METRICS_END_REASON_RESUME);
}
private void cleanup(ChromeActivity activity, int reason) {
private void cleanup(int reason) {
// If `mOnLeavePipCallbacks` is empty, it means that the cleanup call happened while Chrome
// was not PIP'ing. The early return also avoid recording the reason why the PIP session
// ended.
......@@ -301,8 +277,8 @@ public class PictureInPictureController {
// This method can be called when we haven't been PiPed. We use Callbacks to ensure we only
// do cleanup if it is required.
for (Callback<ChromeActivity> callback : mOnLeavePipCallbacks) {
callback.onResult(activity);
for (Runnable callback : mOnLeavePipCallbacks) {
callback.run();
}
mOnLeavePipCallbacks.clear();
......@@ -311,21 +287,21 @@ public class PictureInPictureController {
}
/** Moves the Activity to the back and performs all cleanup. */
private void dismissActivity(ChromeActivity activity, int reason) {
private void dismissActivity(Activity activity, int reason) {
activity.moveTaskToBack(true);
cleanup(activity, reason);
cleanup(reason);
}
/**
* A class to dismiss the Activity when the tab:
* - Closes.
* - Reparents: Attaches to a different activity.
* - Re-parents (attaches to a different activity).
* - Crashes.
* - Leaves fullscreen.
*/
private class DismissActivityOnTabEventObserver extends EmptyTabObserver {
private final ChromeActivity mActivity;
public DismissActivityOnTabEventObserver(ChromeActivity activity) {
private final Activity mActivity;
public DismissActivityOnTabEventObserver(Activity activity) {
mActivity = activity;
}
......@@ -350,21 +326,21 @@ public class PictureInPictureController {
}
}
/** A class to dismiss the Activity when a new tab is created. */
private class DismissActivityOnTabModelSelectorEventObserver
extends EmptyTabModelSelectorObserver {
private final ChromeActivity mActivity;
private final Tab mTab;
public DismissActivityOnTabModelSelectorEventObserver(ChromeActivity activity) {
/** A class to dismiss the Activity when the tab changes. */
private class DismissActivityOnTabChangeObserver
extends ActivityTabProvider.HintlessActivityTabObserver {
private final Activity mActivity;
private final Tab mCurrentTab;
private DismissActivityOnTabChangeObserver(Activity activity, Tab currentTab) {
mActivity = activity;
mTab = activity.getActivityTab();
mCurrentTab = currentTab;
}
@Override
public void onChange() {
if (mActivity.getActivityTab() != mTab) {
dismissActivity(mActivity, METRICS_END_REASON_NEW_TAB);
}
public void onActivityTabChanged(Tab tab) {
if (mCurrentTab == tab) return;
dismissActivity(mActivity, METRICS_END_REASON_NEW_TAB);
}
}
......@@ -374,9 +350,9 @@ public class PictureInPictureController {
* if an iframe without the fullscreen video navigates.
*/
private class DismissActivityOnWebContentsObserver extends WebContentsObserver {
private final ChromeActivity mActivity;
private final Activity mActivity;
public DismissActivityOnWebContentsObserver(ChromeActivity activity) {
public DismissActivityOnWebContentsObserver(Activity activity) {
mActivity = activity;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment