Merge "Provide PipManager through injection."
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user