Allow remote views to be used in Tiles
If a suggeested action Tile has a remote view defined, inflate a card layout to contain the remote view, instead of showing the default title-summary layout. Additionally, if the remote view has a view with ID @android:id/primary, that will be used as the click target rather than the entire card. Test: cd tests/robotests && mma Bug: 35668836 Change-Id: I0fd1b9c637b6d7a3d7d2f6268669917408a882eb
This commit is contained in:
22
res/drawable/selectable_card.xml
Normal file
22
res/drawable/selectable_card.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:drawable="?android:attr/colorBackground"/>
|
||||
</ripple>
|
26
res/layout/suggestion_tile_card.xml
Normal file
26
res/layout/suggestion_tile_card.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/selectable_card"
|
||||
android:clickable="true"
|
||||
android:elevation="2dp"
|
||||
android:focusable="true"
|
||||
android:minHeight="@dimen/dashboard_tile_minimum_height" />
|
@@ -237,6 +237,7 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
||||
mDashboardData.getItemEntityByPosition(position));
|
||||
break;
|
||||
case R.layout.suggestion_tile:
|
||||
case R.layout.suggestion_tile_card:
|
||||
final Tile suggestion = (Tile) mDashboardData.getItemEntityByPosition(position);
|
||||
final String suggestionId = mSuggestionFeatureProvider.getSuggestionIdentifier(
|
||||
mContext, suggestion);
|
||||
@@ -247,7 +248,16 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
||||
mSuggestionsShownLogged.add(suggestionId);
|
||||
}
|
||||
onBindTile(holder, suggestion);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
View clickHandler = holder.itemView;
|
||||
// If a view with @android:id/primary is defined, use that as the click handler
|
||||
// instead.
|
||||
final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
|
||||
if (primaryAction != null) {
|
||||
clickHandler = primaryAction;
|
||||
// set the item view to disabled to remove any touch effects
|
||||
holder.itemView.setEnabled(false);
|
||||
}
|
||||
clickHandler.setOnClickListener(v -> {
|
||||
mMetricsFeatureProvider.action(mContext,
|
||||
MetricsEvent.ACTION_SETTINGS_SUGGESTION, suggestionId);
|
||||
((SettingsActivity) mContext).startSuggestion(suggestion.intent);
|
||||
@@ -405,13 +415,18 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
|
||||
}
|
||||
|
||||
private void onBindTile(DashboardItemHolder holder, Tile tile) {
|
||||
holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
|
||||
holder.title.setText(tile.title);
|
||||
if (!TextUtils.isEmpty(tile.summary)) {
|
||||
holder.summary.setText(tile.summary);
|
||||
holder.summary.setVisibility(View.VISIBLE);
|
||||
if (tile.remoteViews != null) {
|
||||
final ViewGroup itemView = (ViewGroup) holder.itemView;
|
||||
itemView.addView(tile.remoteViews.apply(itemView.getContext(), itemView));
|
||||
} else {
|
||||
holder.summary.setVisibility(View.GONE);
|
||||
holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
|
||||
holder.title.setText(tile.title);
|
||||
if (!TextUtils.isEmpty(tile.summary)) {
|
||||
holder.summary.setText(tile.summary);
|
||||
holder.summary.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.summary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -218,7 +218,13 @@ public class DashboardData {
|
||||
*/
|
||||
private void countSuggestion(Tile tile, boolean add) {
|
||||
if (add) {
|
||||
mItems.add(new Item(tile, R.layout.suggestion_tile, Objects.hash(tile.title), false));
|
||||
mItems.add(new Item(
|
||||
tile,
|
||||
tile.remoteViews != null
|
||||
? R.layout.suggestion_tile_card
|
||||
: R.layout.suggestion_tile,
|
||||
Objects.hash(tile.title),
|
||||
false));
|
||||
}
|
||||
mId++;
|
||||
}
|
||||
|
@@ -65,7 +65,9 @@ public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback
|
||||
|
||||
@Override
|
||||
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
if (viewHolder.getItemViewType() == R.layout.suggestion_tile) {
|
||||
final int layoutId = viewHolder.getItemViewType();
|
||||
if (layoutId == R.layout.suggestion_tile
|
||||
|| layoutId == R.layout.suggestion_tile_card) {
|
||||
// Only return swipe direction for suggestion tiles. All other types are not swipeable.
|
||||
return super.getSwipeDirs(recyclerView, viewHolder);
|
||||
}
|
||||
|
23
tests/robotests/res/drawable/selectable_card.xml
Normal file
23
tests/robotests/res/drawable/selectable_card.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<!-- Overlay this since Robolectric has trouble inflating ?android:attr/colorBackground -->
|
||||
<item android:drawable="@android:color/transparent"/>
|
||||
</ripple>
|
@@ -15,14 +15,32 @@
|
||||
*/
|
||||
package com.android.settings.dashboard;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.RuntimeEnvironment.application;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SettingsRobolectricTestRunner;
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.dashboard.conditional.Condition;
|
||||
@@ -39,18 +57,13 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowApplication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH,
|
||||
sdk = TestConfig.SDK_VERSION,
|
||||
@@ -62,7 +75,7 @@ import static org.mockito.Mockito.when;
|
||||
public class DashboardAdapterTest {
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private Context mContext;
|
||||
private SettingsActivity mContext;
|
||||
@Mock
|
||||
private View mView;
|
||||
@Mock
|
||||
@@ -109,7 +122,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_NotExpanded() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
verify(mFactory.metricsFeatureProvider, times(2)).action(
|
||||
any(Context.class), mActionCategoryCaptor.capture(),
|
||||
mActionPackageCaptor.capture());
|
||||
@@ -124,7 +137,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_NotExpandedAndPaused() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
mDashboardAdapter.onPause();
|
||||
verify(mFactory.metricsFeatureProvider, times(4)).action(
|
||||
any(Context.class), mActionCategoryCaptor.capture(),
|
||||
@@ -141,7 +154,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_Expanded() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
mSuggestionHolder.itemView.callOnClick();
|
||||
@@ -160,7 +173,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedAndPaused() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
mSuggestionHolder.itemView.callOnClick();
|
||||
@@ -183,7 +196,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedAfterPause() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
mDashboardAdapter.onPause();
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
@@ -208,7 +221,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedAfterPauseAndPausedAgain() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1", "pkg2", "pkg3"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1", "pkg2", "pkg3"));
|
||||
mDashboardAdapter.onPause();
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
@@ -237,7 +250,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedWithLessThanDefaultShown() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1"));
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
mSuggestionHolder.itemView.callOnClick();
|
||||
@@ -254,7 +267,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedWithLessThanDefaultShownAndPaused() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1"));
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
mSuggestionHolder.itemView.callOnClick();
|
||||
@@ -273,7 +286,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedWithLessThanDefaultShownAfterPause() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1"));
|
||||
mDashboardAdapter.onPause();
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
@@ -293,7 +306,7 @@ public class DashboardAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testSuggestionsLogs_ExpandedWithLessThanDefaultShownAfterPauseAndPausedAgain() {
|
||||
setUpSuggestions(makeSuggestions(new String[]{"pkg1"}));
|
||||
setUpSuggestions(makeSuggestions("pkg1"));
|
||||
mDashboardAdapter.onPause();
|
||||
mDashboardAdapter.onBindSuggestionHeader(
|
||||
mSuggestionHolder, mSuggestionHeaderData);
|
||||
@@ -313,7 +326,54 @@ public class DashboardAdapterTest {
|
||||
assertThat(mActionCategoryCaptor.getAllValues().toArray()).isEqualTo(expectedActions);
|
||||
}
|
||||
|
||||
private List<Tile> makeSuggestions(String[] pkgNames) {
|
||||
@Test
|
||||
public void testBindViewHolder_inflateRemoteView() {
|
||||
List<Tile> packages = makeSuggestions("pkg1");
|
||||
RemoteViews remoteViews = mock(RemoteViews.class);
|
||||
TextView textView = new TextView(application);
|
||||
doReturn(textView).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
|
||||
packages.get(0).remoteViews = remoteViews;
|
||||
mDashboardAdapter.setCategoriesAndSuggestions(Collections.emptyList(), packages);
|
||||
mSuggestionHolder = mDashboardAdapter.onCreateViewHolder(
|
||||
new FrameLayout(application),
|
||||
R.layout.suggestion_tile_card);
|
||||
|
||||
mDashboardAdapter.onBindViewHolder(mSuggestionHolder, 1);
|
||||
assertThat(textView.getParent()).isSameAs(mSuggestionHolder.itemView);
|
||||
mSuggestionHolder.itemView.performClick();
|
||||
|
||||
verify(mContext).startSuggestion(any(Intent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindViewHolder_primaryViewHandlesClick() {
|
||||
Context context = new ContextThemeWrapper(application, R.style.Theme_Settings);
|
||||
|
||||
List<Tile> packages = makeSuggestions("pkg1");
|
||||
RemoteViews remoteViews = mock(RemoteViews.class);
|
||||
FrameLayout layout = new FrameLayout(context);
|
||||
Button primary = new Button(context);
|
||||
primary.setId(android.R.id.primary);
|
||||
layout.addView(primary);
|
||||
doReturn(layout).when(remoteViews).apply(any(Context.class), any(ViewGroup.class));
|
||||
packages.get(0).remoteViews = remoteViews;
|
||||
mDashboardAdapter.setCategoriesAndSuggestions(Collections.emptyList(), packages);
|
||||
mSuggestionHolder = mDashboardAdapter.onCreateViewHolder(
|
||||
new FrameLayout(context),
|
||||
R.layout.suggestion_tile_card);
|
||||
|
||||
mDashboardAdapter.onBindViewHolder(mSuggestionHolder, 1);
|
||||
|
||||
mSuggestionHolder.itemView.performClick();
|
||||
assertThat(ShadowApplication.getInstance().getNextStartedActivity()).isNull();
|
||||
verify(mContext, never()).startSuggestion(any(Intent.class));
|
||||
|
||||
primary.performClick();
|
||||
|
||||
verify(mContext).startSuggestion(any(Intent.class));
|
||||
}
|
||||
|
||||
private List<Tile> makeSuggestions(String... pkgNames) {
|
||||
final List<Tile> suggestions = new ArrayList<>();
|
||||
for (String pkgName : pkgNames) {
|
||||
Tile suggestion = new Tile();
|
||||
@@ -327,7 +387,7 @@ public class DashboardAdapterTest {
|
||||
private void setUpSuggestions(List<Tile> suggestions) {
|
||||
mDashboardAdapter.setCategoriesAndSuggestions(new ArrayList<>(), suggestions);
|
||||
mSuggestionHolder = mDashboardAdapter.onCreateViewHolder(
|
||||
new FrameLayout(RuntimeEnvironment.application),
|
||||
new FrameLayout(application),
|
||||
mDashboardAdapter.getItemViewType(0));
|
||||
}
|
||||
|
||||
|
@@ -85,6 +85,15 @@ public class SuggestionDismissControllerTest {
|
||||
.isEqualTo(ItemTouchHelper.START | ItemTouchHelper.END);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSwipeDirs_isSuggestionTileCard_shouldReturnDirection() {
|
||||
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
|
||||
when(vh.getItemViewType()).thenReturn(R.layout.suggestion_tile_card);
|
||||
|
||||
assertThat(mController.getSwipeDirs(mRecyclerView, vh))
|
||||
.isEqualTo(ItemTouchHelper.START | ItemTouchHelper.END);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSwipeDirs_isNotSuggestionTile_shouldReturn0() {
|
||||
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
|
||||
|
Reference in New Issue
Block a user