diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a8101f1fe93..08c4985d73f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2983,6 +2983,17 @@ + + + + + + + + * Displays Media output item + *

+ */ +public class MediaOutputPanel implements PanelContent { + + private final Context mContext; + private final String mPackageName; + + public static MediaOutputPanel create(Context context, String packageName) { + return new MediaOutputPanel(context, packageName); + } + + private MediaOutputPanel(Context context, String packageName) { + mContext = context.getApplicationContext(); + mPackageName = packageName; + } + + @Override + public CharSequence getTitle() { + return mContext.getText(R.string.media_output_panel_title); + } + + @Override + public List getSlices() { + final List uris = new ArrayList<>(); + MEDIA_OUTPUT_SLICE_URI = + MEDIA_OUTPUT_SLICE_URI + .buildUpon() + .clearQuery() + .appendQueryParameter(MEDIA_PACKAGE_NAME, mPackageName) + .build(); + uris.add(MEDIA_OUTPUT_SLICE_URI); + return uris; + } + + @Override + public Intent getSeeMoreIntent() { + return null; + } +} diff --git a/src/com/android/settings/panel/PanelFeatureProvider.java b/src/com/android/settings/panel/PanelFeatureProvider.java index 7d6c5581d25..5af5ac8979b 100644 --- a/src/com/android/settings/panel/PanelFeatureProvider.java +++ b/src/com/android/settings/panel/PanelFeatureProvider.java @@ -21,7 +21,7 @@ import android.content.Context; public interface PanelFeatureProvider { /** - * Returns {@link PanelContent} as specified by the {@param panelType}. + * Returns {@link PanelContent} as specified by the {@code panelType} and {@code packageName}. */ - PanelContent getPanel(Context context, String panelType); + PanelContent getPanel(Context context, String panelType, String packageName); } diff --git a/src/com/android/settings/panel/PanelFeatureProviderImpl.java b/src/com/android/settings/panel/PanelFeatureProviderImpl.java index b4c37bf39a1..c3d611dc515 100644 --- a/src/com/android/settings/panel/PanelFeatureProviderImpl.java +++ b/src/com/android/settings/panel/PanelFeatureProviderImpl.java @@ -16,13 +16,15 @@ package com.android.settings.panel; +import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT; + import android.content.Context; import android.provider.Settings; public class PanelFeatureProviderImpl implements PanelFeatureProvider { @Override - public PanelContent getPanel(Context context, String panelType) { + public PanelContent getPanel(Context context, String panelType, String packageName) { switch (panelType) { case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: return InternetConnectivityPanel.create(context); @@ -30,6 +32,8 @@ public class PanelFeatureProviderImpl implements PanelFeatureProvider { return VolumePanel.create(context); case Settings.Panel.ACTION_NFC: return NfcPanel.create(context); + case ACTION_MEDIA_OUTPUT: + return MediaOutputPanel.create(context, packageName); } throw new IllegalStateException("No matching panel for: " + panelType); diff --git a/src/com/android/settings/panel/PanelFragment.java b/src/com/android/settings/panel/PanelFragment.java index 3d302fc011b..db0bf0e85ac 100644 --- a/src/com/android/settings/panel/PanelFragment.java +++ b/src/com/android/settings/panel/PanelFragment.java @@ -70,10 +70,12 @@ public class PanelFragment extends Fragment { final Bundle arguments = getArguments(); final String panelType = arguments.getString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT); + final String packageName = + arguments.getString(SettingsPanelActivity.KEY_PANEL_PACKAGE_NAME); final PanelContent panel = FeatureFactory.getFactory(activity) .getPanelFeatureProvider() - .getPanel(activity, panelType); + .getPanel(activity, panelType, packageName); mAdapter = new PanelSlicesAdapter(this, panel.getSlices()); @@ -86,6 +88,11 @@ public class PanelFragment extends Fragment { mSeeMoreButton.setOnClickListener(getSeeMoreListener(panel.getSeeMoreIntent())); mDoneButton.setOnClickListener(mDoneButtonListener); + //If getSeeMoreIntent() is null, hide the mSeeMoreButton. + if (panel.getSeeMoreIntent() == null) { + mSeeMoreButton.setVisibility(View.GONE); + } + return view; } diff --git a/src/com/android/settings/panel/SettingsPanelActivity.java b/src/com/android/settings/panel/SettingsPanelActivity.java index 02e14e80aaa..4cf535ede11 100644 --- a/src/com/android/settings/panel/SettingsPanelActivity.java +++ b/src/com/android/settings/panel/SettingsPanelActivity.java @@ -16,8 +16,12 @@ package com.android.settings.panel; +import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT; +import static com.android.settingslib.media.MediaOutputSliceConstants.EXTRA_PACKAGE_NAME; + import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; import android.util.Log; import android.view.Gravity; import android.view.Window; @@ -28,6 +32,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; /** @@ -37,10 +42,14 @@ public class SettingsPanelActivity extends FragmentActivity { private final String TAG = "panel_activity"; + @VisibleForTesting + final Bundle mBundle = new Bundle(); + /** * Key specifying which Panel the app is requesting. */ public static final String KEY_PANEL_TYPE_ARGUMENT = "PANEL_TYPE_ARGUMENT"; + public static final String KEY_PANEL_PACKAGE_NAME = "PANEL_PACKAGE_NAME"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -53,6 +62,16 @@ public class SettingsPanelActivity extends FragmentActivity { return; } + final String packageName = + callingIntent.getStringExtra(EXTRA_PACKAGE_NAME); + + if (TextUtils.equals(ACTION_MEDIA_OUTPUT, callingIntent.getAction()) + && TextUtils.isEmpty(packageName)) { + Log.e(TAG, "Null package name, closing Panel Activity"); + finish(); + return; + } + setContentView(R.layout.settings_panel); // Move the window to the bottom of screen, and make it take up the entire screen width. @@ -61,11 +80,11 @@ public class SettingsPanelActivity extends FragmentActivity { window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); - final Bundle bundle = new Bundle(); - bundle.putString(KEY_PANEL_TYPE_ARGUMENT, callingIntent.getAction()); + mBundle.putString(KEY_PANEL_TYPE_ARGUMENT, callingIntent.getAction()); + mBundle.putString(KEY_PANEL_PACKAGE_NAME, packageName); final PanelFragment panelFragment = new PanelFragment(); - panelFragment.setArguments(bundle); + panelFragment.setArguments(mBundle); final FragmentManager fragmentManager = getSupportFragmentManager(); final Fragment fragment = fragmentManager.findFragmentById(R.id.main_content); diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java index 8d07276fab7..3786c5c2ed9 100644 --- a/src/com/android/settings/slices/CustomSliceManager.java +++ b/src/com/android/settings/slices/CustomSliceManager.java @@ -18,6 +18,7 @@ package com.android.settings.slices; import android.content.Context; import android.net.Uri; +import android.text.TextUtils; import android.util.ArrayMap; import androidx.annotation.VisibleForTesting; @@ -33,6 +34,7 @@ import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlic import com.android.settings.homepage.contextualcards.slices.LowStorageSlice; import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice; import com.android.settings.location.LocationSlice; +import com.android.settings.media.MediaOutputSlice; import com.android.settings.wifi.slice.ContextualWifiSlice; import com.android.settings.wifi.slice.WifiSlice; @@ -65,20 +67,25 @@ public class CustomSliceManager { * the only thing that should be needed to create the object. */ public CustomSliceable getSliceableFromUri(Uri uri) { - if (mSliceableCache.containsKey(uri)) { - return mSliceableCache.get(uri); + final Uri newUri = removeParameterFromUri(uri); + if (mSliceableCache.containsKey(newUri)) { + return mSliceableCache.get(newUri); } - final Class clazz = mUriMap.get(uri); + final Class clazz = mUriMap.get(newUri); if (clazz == null) { throw new IllegalArgumentException("No Slice found for uri: " + uri); } final CustomSliceable sliceable = CustomSliceable.createInstance(mContext, clazz); - mSliceableCache.put(uri, sliceable); + mSliceableCache.put(newUri, sliceable); return sliceable; } + private Uri removeParameterFromUri(Uri uri) { + return uri != null ? uri.buildUpon().clearQuery().build() : null; + } + /** * Return a {@link CustomSliceable} associated to the Action. *

@@ -94,7 +101,7 @@ public class CustomSliceManager { * {@link CustomSliceManager}. */ public boolean isValidUri(Uri uri) { - return mUriMap.containsKey(uri); + return mUriMap.containsKey(removeParameterFromUri(uri)); } /** @@ -120,5 +127,6 @@ public class CustomSliceManager { NotificationChannelSlice.class); mUriMap.put(CustomSliceRegistry.STORAGE_SLICE_URI, StorageSlice.class); mUriMap.put(CustomSliceRegistry.WIFI_SLICE_URI, WifiSlice.class); + mUriMap.put(CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class); } } diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index 3a213df6742..66e85c0612d 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -27,6 +27,7 @@ import android.provider.SettingsSlicesContract; import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; import com.android.settings.wifi.calling.WifiCallingSliceHelper; +import com.android.settingslib.media.MediaOutputSliceConstants; /** * A registry of custom slice Uris. @@ -255,4 +256,14 @@ public class CustomSliceRegistry { .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath(ZEN_MODE_KEY) .build(); + + /** + * Backing Uri for the Media output Slice. + */ + public static Uri MEDIA_OUTPUT_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(MediaOutputSliceConstants.KEY_MEDIA_OUTPUT) + .build(); } diff --git a/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java b/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java new file mode 100644 index 00000000000..b4110376226 --- /dev/null +++ b/tests/robotests/src/com/android/settings/panel/MediaOutputPanelTest.java @@ -0,0 +1,65 @@ +/* + * 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.settings.panel; + +import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; + +import com.android.settings.slices.CustomSliceRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class MediaOutputPanelTest { + + private static final String TEST_PACKAGENAME = "com.test.packagename"; + + private MediaOutputPanel mPanel; + + @Before + public void setUp() { + mPanel = MediaOutputPanel.create(RuntimeEnvironment.application, TEST_PACKAGENAME); + } + + @Test + public void getSlices_containsNecessarySlices() { + final List uris = mPanel.getSlices(); + + assertThat(uris).containsExactly(CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI); + } + + @Test + public void getSlices_verifyPackageName_isEqual() { + final List uris = mPanel.getSlices(); + + assertThat(uris.get(0).getQueryParameter(MEDIA_PACKAGE_NAME)).isEqualTo(TEST_PACKAGENAME); + } + + @Test + public void getSeeMoreIntent_isNull() { + assertThat(mPanel.getSeeMoreIntent()).isNull(); + } +} diff --git a/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java index 99d5d6cea55..ae57a778b61 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelFeatureProviderImplTest.java @@ -17,6 +17,8 @@ package com.android.settings.panel; +import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -32,6 +34,8 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PanelFeatureProviderImplTest { + private static final String TEST_PACKAGENAME = "com.test.packagename"; + private Context mContext; private PanelFeatureProviderImpl mProvider; @@ -44,7 +48,7 @@ public class PanelFeatureProviderImplTest { @Test public void getPanel_internetConnectivityKey_returnsCorrectPanel() { final PanelContent panel = mProvider.getPanel(mContext, - Settings.Panel.ACTION_INTERNET_CONNECTIVITY); + Settings.Panel.ACTION_INTERNET_CONNECTIVITY, TEST_PACKAGENAME); assertThat(panel).isInstanceOf(InternetConnectivityPanel.class); } @@ -52,8 +56,16 @@ public class PanelFeatureProviderImplTest { @Test public void getPanel_volume_returnsCorrectPanel() { final PanelContent panel = mProvider.getPanel(mContext, - Settings.Panel.ACTION_VOLUME); + Settings.Panel.ACTION_VOLUME, TEST_PACKAGENAME); assertThat(panel).isInstanceOf(VolumePanel.class); } + + @Test + public void getPanel_mediaOutputKey_returnsCorrectPanel() { + final PanelContent panel = mProvider.getPanel(mContext, + ACTION_MEDIA_OUTPUT, TEST_PACKAGENAME); + + assertThat(panel).isInstanceOf(MediaOutputPanel.class); + } } diff --git a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java index 73318f2ac11..389c31e621c 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java @@ -59,7 +59,7 @@ public class PanelFragmentTest { mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider; mFakePanelContent = new FakePanelContent(); - doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any()); + doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any(), any()); ActivityController activityController = Robolectric.buildActivity(FakeSettingsPanelActivity.class); diff --git a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java index 7648d236720..abefa674623 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java @@ -58,7 +58,7 @@ public class PanelSlicesAdapterTest { mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider; mFakePanelContent = new FakePanelContent(); - doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any()); + doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any(), any()); ActivityController activityController = Robolectric.buildActivity(FakeSettingsPanelActivity.class); diff --git a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java new file mode 100644 index 00000000000..359cf5d7bc3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java @@ -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.settings.panel; + +import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_PACKAGE_NAME; +import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Intent; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class SettingsPanelActivityTest { + + @Test + public void startMediaOutputSlice_withPackageName_bundleShouldHaveValue() { + final Intent intent = new Intent() + .setAction("com.android.settings.panel.action.MEDIA_OUTPUT") + .putExtra("com.android.settings.panel.extra.PACKAGE_NAME", + "com.google.android.music"); + + final SettingsPanelActivity activity = + Robolectric.buildActivity(SettingsPanelActivity.class, intent).create().get(); + + assertThat(activity.mBundle.getString(KEY_PANEL_PACKAGE_NAME)) + .isEqualTo("com.google.android.music"); + assertThat(activity.mBundle.getString(KEY_PANEL_TYPE_ARGUMENT)) + .isEqualTo("com.android.settings.panel.action.MEDIA_OUTPUT"); + } + + @Test + public void startMediaOutputSlice_withoutPackageName_bundleShouldNotHaveValue() { + final Intent intent = new Intent() + .setAction("com.android.settings.panel.action.MEDIA_OUTPUT"); + + final SettingsPanelActivity activity = + Robolectric.buildActivity(SettingsPanelActivity.class, intent).create().get(); + + assertThat(activity.mBundle.containsKey(KEY_PANEL_PACKAGE_NAME)).isFalse(); + assertThat(activity.mBundle.containsKey(KEY_PANEL_TYPE_ARGUMENT)).isFalse(); + } +}