--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingBottom="24dp"
+ android:paddingTop="18dp"
+ android:textColor="?android:attr/colorPrimary"
+ android:textSize="20sp"/>
+
+ <include layout="@layout/horizontal_divider"/>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/panel_parent_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <include layout="@layout/panel_buttons"/>
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="14dp"
+ android:paddingBottom="14dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/see_more"
+ style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dp"
+ android:text="@string/see_more"/>
+
+ <Space
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent" />
+
+ <Button
+ android:id="@+id/done"
+ style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="20dp"
+ android:text="@string/done"/>
+</LinearLayout>
\ No newline at end of file
See the License for the specific language governing permissions and
limitations under the License
-->
+
+<!-- Note: There is a landscape version of this layout. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/panel_parent_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingLeft="24dp"
- android:paddingRight="24dp"
- android:paddingBottom="8dp"
- android:layout_margin="4dp"
- android:orientation="vertical">
+ android:gravity="center"
+ android:paddingBottom="24dp"
+ android:paddingTop="18dp"
+ android:textColor="?android:attr/colorPrimary"
+ android:textSize="20sp"/>
+
+ <include layout="@layout/horizontal_divider"/>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/panel_parent_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <include layout="@layout/panel_buttons"/>
</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <androidx.slice.widget.SliceView
+ android:id="@+id/slice_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp" />
+
+ <include layout="@layout/horizontal_divider"/>
+</LinearLayout>
\ No newline at end of file
<style name="Theme.BottomDialog" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
<item name="android:windowBackground">@drawable/settings_panel_background</item>
+ <item name="android:dividerHorizontal">@*android:drawable/list_divider_material</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
</style>
</resources>
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.slices.CustomSliceRegistry;
@Override
public Intent getSeeMoreIntent() {
- return null;
+ return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
}
}
package com.android.settings.panel;
-import android.net.Uri;
+import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
+import android.widget.Button;
+import android.widget.TextView;
-import androidx.lifecycle.LiveData;
-import androidx.slice.Slice;
-import androidx.slice.widget.SliceLiveData;
-import androidx.slice.widget.SliceView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import androidx.annotation.NonNull;
import com.android.settings.overlay.FeatureFactory;
-import java.util.ArrayList;
-import java.util.List;
-
public class PanelFragment extends Fragment {
private static final String TAG = "PanelFragment";
- private List<SliceView> mSliceViewList;
- private List<LiveData<Slice>> mSliceDataList;
- private LinearLayout mPanelLayout;
+ private TextView mTitleView;
+ private Button mSeeMoreButton;
+ private Button mDoneButton;
+ private RecyclerView mPanelSlices;
+
+ @VisibleForTesting
+ PanelSlicesAdapter mAdapter;
+
+ private View.OnClickListener mDoneButtonListener = (v) -> {
+ Log.d(TAG, "Closing dialog");
+ getActivity().finish();
+ };
public PanelFragment() {
- mSliceViewList = new ArrayList<>();
- mSliceDataList = new ArrayList<>();
}
@Nullable
final FragmentActivity activity = getActivity();
final View view = inflater.inflate(R.layout.panel_layout, container, false);
- mPanelLayout = view.findViewById(R.id.panel_parent_layout);
- final Bundle arguments = getArguments();
+ mPanelSlices = view.findViewById(R.id.panel_parent_layout);
+ mSeeMoreButton = view.findViewById(R.id.see_more);
+ mDoneButton = view.findViewById(R.id.done);
+ mTitleView = view.findViewById(R.id.title);
+ final Bundle arguments = getArguments();
final String panelType = arguments.getString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT);
final PanelContent panel = FeatureFactory.getFactory(activity)
.getPanelFeatureProvider()
.getPanel(activity, panelType);
- activity.setTitle(panel.getTitle());
+ mAdapter = new PanelSlicesAdapter(this, panel.getSlices());
+ mPanelSlices.setHasFixedSize(true);
+ mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
+ mPanelSlices.setAdapter(mAdapter);
- for (Uri uri : panel.getSlices()) {
- final SliceView sliceView = new SliceView(activity);
- mPanelLayout.addView(sliceView);
- final LiveData<Slice> liveData = SliceLiveData.fromUri(activity, uri);
- liveData.observe(this /* lifecycleOwner */, sliceView);
+ mTitleView.setText(panel.getTitle());
- mSliceDataList.add(liveData);
- mSliceViewList.add(sliceView);
- }
+ mSeeMoreButton.setOnClickListener(getSeeMoreListener(panel.getSeeMoreIntent()));
+ mDoneButton.setOnClickListener(mDoneButtonListener);
return view;
}
+
+ private View.OnClickListener getSeeMoreListener(final Intent intent) {
+ return (v) -> {
+ final FragmentActivity activity = getActivity();
+ activity.startActivity(intent);
+ activity.finish();
+ };
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2018 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 android.content.Context;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RecyclerView adapter for Slices in Settings Panels.
+ */
+public class PanelSlicesAdapter
+ extends RecyclerView.Adapter<PanelSlicesAdapter.SliceRowViewHolder> {
+
+ private final List<Uri> mSliceUris;
+ private final PanelFragment mPanelFragment;
+
+ public PanelSlicesAdapter(PanelFragment fragment, List<Uri> sliceUris) {
+ mPanelFragment = fragment;
+ mSliceUris = new ArrayList<>(sliceUris);
+ }
+
+ @NonNull
+ @Override
+ public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
+ final Context context = viewGroup.getContext();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final View view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false);
+
+ return new SliceRowViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
+ sliceRowViewHolder.onBind(mPanelFragment, mSliceUris.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSliceUris.size();
+ }
+
+ @VisibleForTesting
+ List<Uri> getData() {
+ return mSliceUris;
+ }
+
+ /**
+ * ViewHolder for binding Slices to SliceViews.
+ */
+ public static class SliceRowViewHolder extends RecyclerView.ViewHolder {
+
+ @VisibleForTesting
+ LiveData<Slice> sliceLiveData;
+
+ @VisibleForTesting
+ final SliceView sliceView;
+
+ public SliceRowViewHolder(View view) {
+ super(view);
+ sliceView = view.findViewById(R.id.slice_view);
+ sliceView.setMode(SliceView.MODE_LARGE);
+ }
+
+ public void onBind(PanelFragment fragment, Uri sliceUri) {
+ final Context context = sliceView.getContext();
+ sliceLiveData = SliceLiveData.fromUri(context, sliceUri);
+ sliceLiveData.observe(fragment.getViewLifecycleOwner(), sliceView);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.slices.CustomSliceRegistry.WIFI_SLICE_URI;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Fake PanelContent for testing.
+ */
+public class FakePanelContent implements PanelContent {
+
+ public static final String FAKE_KEY = "fake_key";
+
+ public static final CharSequence TITLE = "title";
+
+ public static final List<Uri> SLICE_URIS = Arrays.asList(
+ WIFI_SLICE_URI
+ );
+
+ public static final Intent INTENT = new Intent();
+
+ @Override
+ public CharSequence getTitle() {
+ return TITLE;
+ }
+
+ @Override
+ public List<Uri> getSlices() {
+ return SLICE_URIS;
+ }
+
+ @Override
+ public Intent getSeeMoreIntent() {
+ return INTENT;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 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 android.content.ComponentName;
+import android.content.Intent;
+
+public class FakeSettingsPanelActivity extends SettingsPanelActivity {
+ @Override
+ public ComponentName getCallingActivity() {
+ return new ComponentName("fake-package", "fake-class");
+ }
+
+ @Override
+ public Intent getIntent() {
+ final Intent intent = new Intent();
+ intent.putExtra(SettingsPanelActivity.EXTRA_PANEL_TYPE, FakePanelContent.FAKE_KEY);
+ return intent;
+ }
+}
\ No newline at end of file
assertThat(uris).containsExactly(CustomSliceRegistry.WIFI_SLICE_URI,
CustomSliceRegistry.AIRPLANE_URI);
}
+
+ @Test
+ public void getSeeMoreIntent_notNull() {
+ assertThat(mPanel.getSeeMoreIntent()).isNotNull();
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2018 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class PanelFragmentTest {
+
+ private Context mContext;
+ private PanelFragment mPanelFragment;
+ private FakeFeatureFactory mFakeFeatureFactory;
+ private PanelFeatureProvider mPanelFeatureProvider;
+ private FakePanelContent mFakePanelContent;
+
+ private final String FAKE_EXTRA = "fake_extra";
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+
+ mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
+ mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+ mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider;
+ mFakePanelContent = new FakePanelContent();
+ doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any());
+
+
+ ActivityController<FakeSettingsPanelActivity> activityController =
+ Robolectric.buildActivity(FakeSettingsPanelActivity.class);
+ activityController.setup();
+
+ mPanelFragment =
+ spy((PanelFragment)
+ activityController
+ .get()
+ .getSupportFragmentManager()
+ .findFragmentById(R.id.main_content));
+ }
+
+ @Test
+ public void onCreateView_adapterGetsDataset() {
+ final Bundle bundle = new Bundle();
+ bundle.putString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT, FAKE_EXTRA);
+ doReturn(bundle).when(mPanelFragment).getArguments();
+ mPanelFragment.onCreateView(LayoutInflater.from(mContext),
+ new LinearLayout(mContext), null);
+ PanelSlicesAdapter adapter = mPanelFragment.mAdapter;
+
+ assertThat(adapter.getData()).containsAllIn(mFakePanelContent.getSlices());
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2018 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+
+import org.junit.Test;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class PanelSlicesAdapterTest {
+
+ private Context mContext;
+ private PanelFragment mPanelFragment;
+ private FakePanelContent mFakePanelContent;
+ private FakeFeatureFactory mFakeFeatureFactory;
+ private PanelFeatureProvider mPanelFeatureProvider;
+
+ private PanelSlicesAdapter mAdapter;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+
+ final ActivityController<FakeSettingsPanelActivity> activityController =
+ Robolectric.buildActivity(FakeSettingsPanelActivity.class);
+ activityController.setup();
+
+ mPanelFragment =
+ spy((PanelFragment)
+ activityController
+ .get()
+ .getSupportFragmentManager()
+ .findFragmentById(R.id.main_content));
+
+ mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
+ mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+ mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider;
+ mFakePanelContent = new FakePanelContent();
+ doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any());
+
+ mAdapter = new PanelSlicesAdapter(mPanelFragment, mFakePanelContent.getSlices());
+ }
+
+ @Test
+ public void onCreateViewHolder_returnsSliceRowViewHolder() {
+ final ViewGroup view = new FrameLayout(mContext);
+ final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
+ mAdapter.onCreateViewHolder(view, 0);
+
+ assertThat(viewHolder.sliceView).isNotNull();
+ }
+
+ @Test
+ public void onBindViewHolder_bindsSlice() {
+ final int position = 0;
+ final ViewGroup view = new FrameLayout(mContext);
+ final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
+ mAdapter.onCreateViewHolder(view, 0 /* view type*/);
+
+ mAdapter.onBindViewHolder(viewHolder, position);
+
+ assertThat(viewHolder.sliceLiveData).isNotNull();
+ }
+}
\ No newline at end of file
public final UserFeatureProvider userFeatureProvider;
public final AssistGestureFeatureProvider assistGestureFeatureProvider;
public final AccountFeatureProvider mAccountFeatureProvider;
- public final PanelFeatureProvider mPanelFeatureProvider;
public final ContextualCardFeatureProvider mContextualCardFeatureProvider;
+ public PanelFeatureProvider panelFeatureProvider;
public SlicesFeatureProvider slicesFeatureProvider;
public SearchFeatureProvider searchFeatureProvider;
assistGestureFeatureProvider = mock(AssistGestureFeatureProvider.class);
slicesFeatureProvider = mock(SlicesFeatureProvider.class);
mAccountFeatureProvider = mock(AccountFeatureProvider.class);
- mPanelFeatureProvider = mock(PanelFeatureProvider.class);
mContextualCardFeatureProvider = mock(ContextualCardFeatureProvider.class);
+ panelFeatureProvider = mock(PanelFeatureProvider.class);
}
@Override
@Override
public PanelFeatureProvider getPanelFeatureProvider() {
- return mPanelFeatureProvider;
+ return panelFeatureProvider;
}
public ContextualCardFeatureProvider getContextualCardFeatureProvider() {