Merge "Provide PipManager through injection."

This commit is contained in:
Dave Mankoff
2020-02-18 16:14:53 +00:00
committed by Android (Google) Code Review
15 changed files with 455 additions and 329 deletions

View File

@@ -21,6 +21,7 @@ import com.android.systemui.dagger.DependencyProvider;
import com.android.systemui.dagger.SystemServicesModule;
import com.android.systemui.dagger.SystemUIModule;
import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.pip.phone.dagger.PipModule;
import javax.inject.Singleton;
@@ -32,6 +33,7 @@ import dagger.Component;
CarComponentBinder.class,
DependencyProvider.class,
DependencyBinder.class,
PipModule.class,
SystemUIFactory.ContextHolder.class,
SystemServicesModule.class,
SystemUIModule.class,

View File

@@ -21,8 +21,9 @@
for different hardware and product builds. -->
<resources>
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.tv.TvSystemUIFactory</string>
<string name="config_systemUIFactoryComponent" translatable="false">
com.android.systemui.tv.TvSystemUIFactory
</string>
<!-- SystemUI Services: The classes of the stuff to start. -->
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</item>

View File

@@ -27,6 +27,7 @@ import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.pip.phone.dagger.PipModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.InjectionInflationController;
@@ -43,6 +44,7 @@ import dagger.Component;
DefaultComponentBinder.class,
DependencyProvider.class,
DependencyBinder.class,
PipModule.class,
SystemServicesModule.class,
SystemUIFactory.ContextHolder.class,
SystemUIBinder.class,

View File

@@ -16,17 +16,11 @@
package com.android.systemui.pip;
import android.content.Context;
import android.content.res.Configuration;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.wm.DisplayController;
import java.io.PrintWriter;
public interface BasePipManager {
void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
DisplayController displayController);
void showPictureInPictureMenu();
default void expandPip() {}
default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}

View File

@@ -16,7 +16,6 @@
package com.android.systemui.pip;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import android.content.Context;
@@ -26,9 +25,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.wm.DisplayController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,25 +41,20 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks {
private final CommandQueue mCommandQueue;
private BasePipManager mPipManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayController mDisplayController;
private boolean mSupportsPip;
@Inject
public PipUI(Context context, CommandQueue commandQueue,
BroadcastDispatcher broadcastDispatcher,
DisplayController displayController) {
BasePipManager pipManager) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
mDisplayController = displayController;
mPipManager = pipManager;
}
@Override
public void start() {
PackageManager pm = mContext.getPackageManager();
mSupportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
if (!mSupportsPip) {
boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
if (!supportsPip) {
return;
}
@@ -72,11 +64,6 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks {
throw new IllegalStateException("Non-primary Pip component not currently supported.");
}
mPipManager = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY)
? com.android.systemui.pip.tv.PipManager.getInstance()
: com.android.systemui.pip.phone.PipManager.getInstance();
mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayController);
mCommandQueue.addCallback(this);
}

View File

@@ -51,14 +51,16 @@ import com.android.systemui.wm.DisplayController;
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
@Singleton
public class PipManager implements BasePipManager {
private static final String TAG = "PipManager";
private static PipManager sPipController;
private Context mContext;
private IActivityManager mActivityManager;
private IActivityTaskManager mActivityTaskManager;
@@ -225,12 +227,8 @@ public class PipManager implements BasePipManager {
}
}
private PipManager() {}
/**
* Initializes {@link PipManager}.
*/
public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
@Inject
public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
DisplayController displayController) {
mContext = context;
mActivityManager = ActivityManager.getService();
@@ -329,16 +327,6 @@ public class PipManager implements BasePipManager {
mTmpDisplayInfo.rotation);
}
/**
* Gets an instance of {@link PipManager}.
*/
public static PipManager getInstance() {
if (sPipController == null) {
sPipController = new PipManager();
}
return sPipController;
}
public void dump(PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG);

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.pip.phone.dagger;
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.pip.phone.PipManager;
import dagger.Binds;
import dagger.Module;
/**
* Dagger Module for Phone PIP.
*/
@Module
public abstract class PipModule {
/** Binds PipManager as the default BasePipManager. */
@Binds
public abstract BasePipManager providePipManager(PipManager pipManager);
}

View File

@@ -16,286 +16,38 @@
package com.android.systemui.pip.tv;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Color;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import com.android.systemui.R;
import java.util.ArrayList;
import java.util.List;
/**
* A view containing PIP controls including fullscreen, close, and media controls.
*/
public class PipControlsView extends LinearLayout {
private static final String TAG = PipControlsView.class.getSimpleName();
private static final float DISABLED_ACTION_ALPHA = 0.54f;
/**
* An interface to listen user action.
*/
public abstract static interface Listener {
/**
* Called when an user clicks close PIP button.
*/
public abstract void onClosed();
};
private MediaController mMediaController;
private final PipManager mPipManager = PipManager.getInstance();
private final LayoutInflater mLayoutInflater;
private final Handler mHandler;
private Listener mListener;
private PipControlButtonView mFullButtonView;
private PipControlButtonView mCloseButtonView;
private PipControlButtonView mPlayPauseButtonView;
private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
private List<RemoteAction> mCustomActions = new ArrayList<>();
private PipControlButtonView mFocusedChild;
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
updateUserActions();
}
};
private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
@Override
public void onMediaControllerChanged() {
updateMediaController();
}
};
private final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) {
mFocusedChild = (PipControlButtonView) view;
} else if (mFocusedChild == view) {
mFocusedChild = null;
}
}
};
public PipControlsView(Context context) {
this(context, null, 0, 0);
}
public PipControlsView(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mLayoutInflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutInflater.inflate(R.layout.tv_pip_controls, this);
mHandler = new Handler();
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.tv_pip_controls, this);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mFullButtonView = findViewById(R.id.full_button);
mFullButtonView.setOnFocusChangeListener(mFocusChangeListener);
mFullButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPipManager.movePipToFullscreen();
}
});
mCloseButtonView = findViewById(R.id.close_button);
mCloseButtonView.setOnFocusChangeListener(mFocusChangeListener);
mCloseButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPipManager.closePip();
if (mListener != null) {
mListener.onClosed();
}
}
});
mPlayPauseButtonView = findViewById(R.id.play_pause_button);
mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
mPlayPauseButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mMediaController == null || mMediaController.getPlaybackState() == null) {
return;
}
long actions = mMediaController.getPlaybackState().getActions();
int state = mMediaController.getPlaybackState().getState();
if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
mMediaController.getTransportControls().play();
} else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
mMediaController.getTransportControls().pause();
}
// View will be updated later in {@link mMediaControllerCallback}
}
});
PipControlButtonView getFullButtonView() {
return findViewById(R.id.full_button);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
updateMediaController();
mPipManager.addMediaListener(mPipMediaListener);
PipControlButtonView getCloseButtonView() {
return findViewById(R.id.close_button);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPipManager.removeMediaListener(mPipMediaListener);
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
}
private void updateMediaController() {
MediaController newController = mPipManager.getMediaController();
if (mMediaController == newController) {
return;
}
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
mMediaController = newController;
if (mMediaController != null) {
mMediaController.registerCallback(mMediaControllerCallback);
}
updateUserActions();
}
/**
* Updates the actions for the PIP. If there are no custom actions, then the media session
* actions are shown.
*/
private void updateUserActions() {
if (!mCustomActions.isEmpty()) {
// Ensure we have as many buttons as actions
while (mCustomButtonViews.size() < mCustomActions.size()) {
PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
R.layout.tv_pip_custom_control, this, false);
addView(buttonView);
mCustomButtonViews.add(buttonView);
}
// Update the visibility of all views
for (int i = 0; i < mCustomButtonViews.size(); i++) {
mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size()
? View.VISIBLE
: View.GONE);
}
// Update the state and visibility of the action buttons, and hide the rest
for (int i = 0; i < mCustomActions.size(); i++) {
final RemoteAction action = mCustomActions.get(i);
PipControlButtonView actionView = mCustomButtonViews.get(i);
// TODO: Check if the action drawable has changed before we reload it
action.getIcon().loadDrawableAsync(getContext(), d -> {
d.setTint(Color.WHITE);
actionView.setImageDrawable(d);
}, mHandler);
actionView.setText(action.getContentDescription());
if (action.isEnabled()) {
actionView.setOnClickListener(v -> {
try {
action.getActionIntent().send();
} catch (CanceledException e) {
Log.w(TAG, "Failed to send action", e);
}
});
}
actionView.setEnabled(action.isEnabled());
actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
}
// Hide the media session buttons
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
int state = mPipManager.getPlaybackState();
if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
mPlayPauseButtonView.setVisibility(View.VISIBLE);
if (state == PipManager.PLAYBACK_STATE_PLAYING) {
mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
mPlayPauseButtonView.setText(R.string.pip_pause);
} else {
mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
mPlayPauseButtonView.setText(R.string.pip_play);
}
}
// Hide all the custom action buttons
for (int i = 0; i < mCustomButtonViews.size(); i++) {
mCustomButtonViews.get(i).setVisibility(View.GONE);
}
}
}
/**
* Resets to initial state.
*/
public void reset() {
mFullButtonView.reset();
mCloseButtonView.reset();
mPlayPauseButtonView.reset();
mFullButtonView.requestFocus();
for (int i = 0; i < mCustomButtonViews.size(); i++) {
mCustomButtonViews.get(i).reset();
}
}
/**
* Sets the {@link Listener} to listen user actions.
*/
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Updates the set of activity-defined actions.
*/
public void setActions(List<RemoteAction> actions) {
mCustomActions.clear();
mCustomActions.addAll(actions);
updateUserActions();
}
/**
* Returns the focused control button view to animate focused button.
*/
PipControlButtonView getFocusedButton() {
return mFocusedChild;
PipControlButtonView getPlayPauseButtonView() {
return findViewById(R.id.play_pause_button);
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.pip.tv;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.graphics.Color;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
/**
* Controller for {@link PipControlsView}.
*/
public class PipControlsViewController {
private static final String TAG = PipControlsViewController.class.getSimpleName();
private static final float DISABLED_ACTION_ALPHA = 0.54f;
private final PipControlsView mView;
private final PipManager mPipManager;
private final LayoutInflater mLayoutInflater;
private final Handler mHandler;
private final PipControlButtonView mPlayPauseButtonView;
private MediaController mMediaController;
private PipControlButtonView mFocusedChild;
private Listener mListener;
private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
private List<RemoteAction> mCustomActions = new ArrayList<>();
public PipControlsView getView() {
return mView;
}
/**
* An interface to listen user action.
*/
public interface Listener {
/**
* Called when a user clicks close PIP button.
*/
void onClosed();
}
private View.OnAttachStateChangeListener
mOnAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
updateMediaController();
mPipManager.addMediaListener(mPipMediaListener);
}
@Override
public void onViewDetachedFromWindow(View v) {
mPipManager.removeMediaListener(mPipMediaListener);
}
};
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
updateUserActions();
}
};
private final PipManager.MediaListener mPipMediaListener = this::updateMediaController;
private final View.OnFocusChangeListener
mFocusChangeListener =
new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) {
mFocusedChild = (PipControlButtonView) view;
} else if (mFocusedChild == view) {
mFocusedChild = null;
}
}
};
@Inject
public PipControlsViewController(PipControlsView view, PipManager pipManager,
LayoutInflater layoutInflater, @Main Handler handler) {
super();
mView = view;
mPipManager = pipManager;
mLayoutInflater = layoutInflater;
mHandler = handler;
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
View fullButtonView = mView.getFullButtonView();
fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
fullButtonView.setOnClickListener(v -> mPipManager.movePipToFullscreen());
View closeButtonView = mView.getCloseButtonView();
closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
closeButtonView.setOnClickListener(v -> {
mPipManager.closePip();
if (mListener != null) {
mListener.onClosed();
}
});
mPlayPauseButtonView = mView.getPlayPauseButtonView();
mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
mPlayPauseButtonView.setOnClickListener(v -> {
if (mMediaController == null || mMediaController.getPlaybackState() == null) {
return;
}
if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
mMediaController.getTransportControls().play();
} else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
mMediaController.getTransportControls().pause();
}
// View will be updated later in {@link mMediaControllerCallback}
});
}
private void updateMediaController() {
MediaController newController = mPipManager.getMediaController();
if (mMediaController == newController) {
return;
}
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
}
mMediaController = newController;
if (mMediaController != null) {
mMediaController.registerCallback(mMediaControllerCallback);
}
updateUserActions();
}
/**
* Updates the actions for the PIP. If there are no custom actions, then the media session
* actions are shown.
*/
private void updateUserActions() {
if (!mCustomActions.isEmpty()) {
// Ensure we have as many buttons as actions
while (mCustomButtonViews.size() < mCustomActions.size()) {
PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
R.layout.tv_pip_custom_control, mView, false);
mView.addView(buttonView);
mCustomButtonViews.add(buttonView);
}
// Update the visibility of all views
for (int i = 0; i < mCustomButtonViews.size(); i++) {
mCustomButtonViews.get(i).setVisibility(
i < mCustomActions.size() ? View.VISIBLE : View.GONE);
}
// Update the state and visibility of the action buttons, and hide the rest
for (int i = 0; i < mCustomActions.size(); i++) {
final RemoteAction action = mCustomActions.get(i);
PipControlButtonView actionView = mCustomButtonViews.get(i);
// TODO: Check if the action drawable has changed before we reload it
action.getIcon().loadDrawableAsync(mView.getContext(), d -> {
d.setTint(Color.WHITE);
actionView.setImageDrawable(d);
}, mHandler);
actionView.setText(action.getContentDescription());
if (action.isEnabled()) {
actionView.setOnClickListener(v -> {
try {
action.getActionIntent().send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to send action", e);
}
});
}
actionView.setEnabled(action.isEnabled());
actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
}
// Hide the media session buttons
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
int state = mPipManager.getPlaybackState();
if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
mPlayPauseButtonView.setVisibility(View.VISIBLE);
if (state == PipManager.PLAYBACK_STATE_PLAYING) {
mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
mPlayPauseButtonView.setText(R.string.pip_pause);
} else {
mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
mPlayPauseButtonView.setText(R.string.pip_play);
}
}
// Hide all the custom action buttons
for (int i = 0; i < mCustomButtonViews.size(); i++) {
mCustomButtonViews.get(i).setVisibility(View.GONE);
}
}
}
/**
* Sets the {@link Listener} to listen user actions.
*/
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Updates the set of activity-defined actions.
*/
public void setActions(List<RemoteAction> actions) {
mCustomActions.clear();
mCustomActions.addAll(actions);
updateUserActions();
}
}

View File

@@ -55,21 +55,23 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.wm.DisplayController;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
@Singleton
public class PipManager implements BasePipManager {
private static final String TAG = "PipManager";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
private static PipManager sPipManager;
private static List<Pair<String, String>> sSettingsPackageAndClassNamePairList;
/**
@@ -224,13 +226,8 @@ public class PipManager implements BasePipManager {
}
}
private PipManager() { }
/**
* Initializes {@link PipManager}.
*/
public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
DisplayController displayController) {
@Inject
public PipManager(Context context, BroadcastDispatcher broadcastDispatcher) {
if (mInitialized) {
return;
}
@@ -289,7 +286,7 @@ public class PipManager implements BasePipManager {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
mPipNotification = new PipNotification(context, broadcastDispatcher);
mPipNotification = new PipNotification(context, broadcastDispatcher, this);
}
private void loadConfigurationsAndApply(Configuration newConfig) {
@@ -739,16 +736,6 @@ public class PipManager implements BasePipManager {
void onMediaControllerChanged();
}
/**
* Gets an instance of {@link PipManager}.
*/
public static PipManager getInstance() {
if (sPipManager == null) {
sPipManager = new PipManager();
}
return sPipManager;
}
private void updatePipVisibility(final boolean visible) {
Dependency.get(UiOffloadThread.class).execute(() -> {
WindowManagerWrapper.getInstance().setPipVisibility(visible);

View File

@@ -24,8 +24,12 @@ import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import com.android.systemui.R;
import com.android.systemui.pip.tv.dagger.TvPipComponent;
import java.util.Collections;
import javax.inject.Inject;
/**
* Activity to show the PIP menu to control PIP.
*/
@@ -34,12 +38,22 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
private final PipManager mPipManager = PipManager.getInstance();
private final TvPipComponent.Builder mPipComponentBuilder;
private TvPipComponent mTvPipComponent;
private final PipManager mPipManager;
private Animator mFadeInAnimation;
private Animator mFadeOutAnimation;
private PipControlsView mPipControlsView;
private boolean mRestorePipSizeWhenClose;
private PipControlsViewController mPipControlsViewController;
@Inject
public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) {
super();
mPipComponentBuilder = pipComponentBuilder;
mPipManager = pipManager;
}
@Override
protected void onCreate(Bundle bundle) {
@@ -48,16 +62,19 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
finish();
}
setContentView(R.layout.tv_pip_menu);
mTvPipComponent = mPipComponentBuilder.pipControlsView(
findViewById(R.id.pip_controls)).build();
mPipControlsViewController = mTvPipComponent.getPipControlsViewController();
mPipManager.addListener(this);
mRestorePipSizeWhenClose = true;
mPipControlsView = findViewById(R.id.pip_controls);
mFadeInAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_in_animation);
mFadeInAnimation.setTarget(mPipControlsView);
mFadeInAnimation.setTarget(mPipControlsViewController.getView());
mFadeOutAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_out_animation);
mFadeOutAnimation.setTarget(mPipControlsView);
mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
}
@@ -114,7 +131,8 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
@Override
public void onPipMenuActionsChanged(ParceledListSlice actions) {
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
mPipControlsViewController.setActions(
hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
}
@Override

View File

@@ -50,7 +50,7 @@ public class PipNotification {
private static final String ACTION_MENU = "PipNotification.menu";
private static final String ACTION_CLOSE = "PipNotification.close";
private final PipManager mPipManager = PipManager.getInstance();
private final PipManager mPipManager;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
@@ -144,7 +144,8 @@ public class PipNotification {
}
};
public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher) {
public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher,
PipManager pipManager) {
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
@@ -156,6 +157,7 @@ public class PipNotification {
.setContentIntent(createPendingIntent(context, ACTION_MENU))
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
mPipManager = pipManager;
mPipManager.addListener(mPipListener);
mPipManager.addMediaListener(mPipMediaListener);

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.pip.tv.dagger;
import android.app.Activity;
import com.android.systemui.pip.BasePipManager;
import com.android.systemui.pip.tv.PipManager;
import com.android.systemui.pip.tv.PipMenuActivity;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
/**
* Dagger module for TV Pip.
*/
@Module(subcomponents = {TvPipComponent.class})
public abstract class PipModule {
/** Binds PipManager as the default BasePipManager. */
@Binds
public abstract BasePipManager providePipManager(PipManager pipManager);
/** Inject into PipMenuActivity. */
@Binds
@IntoMap
@ClassKey(PipMenuActivity.class)
public abstract Activity providePipMenuActivity(PipMenuActivity activity);
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.pip.tv.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.pip.tv.PipControlsView;
import com.android.systemui.pip.tv.PipControlsViewController;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Scope;
import dagger.BindsInstance;
import dagger.Subcomponent;
/**
* Component for injecting into Pip related classes.
*/
@Subcomponent
public interface TvPipComponent {
/**
* Builder for {@link StatusBarComponent}.
*/
@Subcomponent.Builder
interface Builder {
@BindsInstance
TvPipComponent.Builder pipControlsView(PipControlsView pipControlsView);
TvPipComponent build();
}
/**
* Scope annotation for singleton items within the PipComponent.
*/
@Documented
@Retention(RUNTIME)
@Scope
@interface PipScope {}
/**
* Creates a StatusBarWindowViewController.
*/
@TvPipComponent.PipScope
PipControlsViewController getPipControlsViewController();
}

View File

@@ -17,11 +17,12 @@
package com.android.systemui.tv;
import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.pip.tv.dagger.PipModule;
import dagger.Binds;
import dagger.Module;
@Module
@Module(includes = {PipModule.class})
interface TvSystemUIBinder {
@Binds
SystemUIRootComponent bindSystemUIRootComponent(TvSystemUIRootComponent systemUIRootComponent);