Merge "Add entry animation to Settings Panels." into qt-dev
This commit is contained in:
@@ -15,50 +15,60 @@
|
|||||||
limitations under the License
|
limitations under the License
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Note: There is a landscape version of this layout. -->
|
<FrameLayout
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/panel_container"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:orientation="vertical">
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/settings_panel_background" >
|
||||||
<TextView
|
|
||||||
android:id="@+id/panel_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/panel_slice_list"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical">
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/see_more"
|
android:id="@+id/panel_title"
|
||||||
style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="48dp"
|
android:gravity="center"
|
||||||
android:layout_marginStart="12dp"
|
android:paddingBottom="24dp"
|
||||||
android:text="@string/see_more"/>
|
android:paddingTop="18dp"
|
||||||
|
android:textColor="?android:attr/colorPrimary"
|
||||||
|
android:textSize="20sp"/>
|
||||||
|
|
||||||
<Space
|
<include layout="@layout/horizontal_divider"/>
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<Button
|
<!-- Note: There is a landscape version of panel_slice_list which supports scrolling. -->
|
||||||
android:id="@+id/done"
|
<include layout="@layout/panel_slice_list"/>
|
||||||
style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
|
|
||||||
android:layout_width="wrap_content"
|
<LinearLayout
|
||||||
android:layout_height="48dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/done"/>
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/see_more"
|
||||||
|
style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
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="48dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:text="@string/done"/>
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</FrameLayout>
|
@@ -15,6 +15,5 @@
|
|||||||
-->
|
-->
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/main_content"
|
android:id="@+id/main_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"/>
|
||||||
android:animateLayoutChanges="true"/>
|
|
@@ -203,7 +203,7 @@
|
|||||||
|
|
||||||
<!-- Note that Dialog themes do not set list dividers -->
|
<!-- Note that Dialog themes do not set list dividers -->
|
||||||
<style name="Theme.BottomDialog" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
|
<style name="Theme.BottomDialog" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
|
||||||
<item name="android:windowBackground">@drawable/settings_panel_background</item>
|
<item name="android:windowBackground">@null</item>
|
||||||
<item name="android:dividerHorizontal">@*android:drawable/list_divider_material</item>
|
<item name="android:dividerHorizontal">@*android:drawable/list_divider_material</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
|
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
|
||||||
|
@@ -16,13 +16,20 @@
|
|||||||
|
|
||||||
package com.android.settings.panel;
|
package com.android.settings.panel;
|
||||||
|
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -30,8 +37,12 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.slice.Slice;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.slice.SliceMetadata;
|
||||||
|
import androidx.slice.widget.SliceLiveData;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
@@ -40,10 +51,24 @@ import com.android.settings.panel.PanelLoggingContract.PanelClosedKeys;
|
|||||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||||
import com.google.android.setupdesign.DividerItemDecoration;
|
import com.google.android.setupdesign.DividerItemDecoration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class PanelFragment extends Fragment {
|
public class PanelFragment extends Fragment {
|
||||||
|
|
||||||
private static final String TAG = "PanelFragment";
|
private static final String TAG = "PanelFragment";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of the animation entering or exiting the screen, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static final int DURATION_ANIMATE_PANEL_MS = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of timeout waiting for Slice data to bind, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static final int DURATION_SLICE_BINDING_TIMEOUT_MS = 250;
|
||||||
|
|
||||||
|
private View mLayoutView;
|
||||||
private TextView mTitleView;
|
private TextView mTitleView;
|
||||||
private Button mSeeMoreButton;
|
private Button mSeeMoreButton;
|
||||||
private Button mDoneButton;
|
private Button mDoneButton;
|
||||||
@@ -53,20 +78,40 @@ public class PanelFragment extends Fragment {
|
|||||||
private MetricsFeatureProvider mMetricsProvider;
|
private MetricsFeatureProvider mMetricsProvider;
|
||||||
private String mPanelClosedKey;
|
private String mPanelClosedKey;
|
||||||
|
|
||||||
|
private final List<LiveData<Slice>> mSliceLiveData = new ArrayList<>();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
PanelSlicesAdapter mAdapter;
|
PanelSlicesLoaderCountdownLatch mPanelSlicesLoaderCountdownLatch;
|
||||||
|
|
||||||
|
private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener = () -> {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
|
||||||
|
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
animateIn();
|
||||||
|
if (mPanelSlices != null) {
|
||||||
|
mPanelSlices.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private PanelSlicesAdapter mAdapter;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
final FragmentActivity activity = getActivity();
|
final FragmentActivity activity = getActivity();
|
||||||
final View view = inflater.inflate(R.layout.panel_layout, container, false);
|
|
||||||
|
|
||||||
mPanelSlices = view.findViewById(R.id.panel_parent_layout);
|
mLayoutView = inflater.inflate(R.layout.panel_layout, container, false);
|
||||||
mSeeMoreButton = view.findViewById(R.id.see_more);
|
|
||||||
mDoneButton = view.findViewById(R.id.done);
|
mPanelSlices = mLayoutView.findViewById(R.id.panel_parent_layout);
|
||||||
mTitleView = view.findViewById(R.id.panel_title);
|
mSeeMoreButton = mLayoutView.findViewById(R.id.see_more);
|
||||||
|
mDoneButton = mLayoutView.findViewById(R.id.done);
|
||||||
|
mTitleView = mLayoutView.findViewById(R.id.panel_title);
|
||||||
|
|
||||||
final Bundle arguments = getArguments();
|
final Bundle arguments = getArguments();
|
||||||
final String panelType =
|
final String panelType =
|
||||||
@@ -82,6 +127,24 @@ public class PanelFragment extends Fragment {
|
|||||||
.getPanel(activity, panelType, mediaPackageName);
|
.getPanel(activity, panelType, mediaPackageName);
|
||||||
|
|
||||||
mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
|
mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
|
||||||
|
|
||||||
|
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
|
||||||
|
|
||||||
|
// Add predraw listener to remove the animation and while we wait for Slices to load.
|
||||||
|
mLayoutView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
|
||||||
|
// Start loading Slices. When finished, the Panel will animate in.
|
||||||
|
loadAllSlices();
|
||||||
|
|
||||||
|
mTitleView.setText(mPanel.getTitle());
|
||||||
|
mSeeMoreButton.setOnClickListener(getSeeMoreListener());
|
||||||
|
mDoneButton.setOnClickListener(getCloseListener());
|
||||||
|
|
||||||
|
// If getSeeMoreIntent() is null, hide the mSeeMoreButton.
|
||||||
|
if (mPanel.getSeeMoreIntent() == null) {
|
||||||
|
mSeeMoreButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
// Log panel opened.
|
// Log panel opened.
|
||||||
mMetricsProvider.action(
|
mMetricsProvider.action(
|
||||||
0 /* attribution */,
|
0 /* attribution */,
|
||||||
@@ -90,27 +153,114 @@ public class PanelFragment extends Fragment {
|
|||||||
callingPackageName,
|
callingPackageName,
|
||||||
0 /* value */);
|
0 /* value */);
|
||||||
|
|
||||||
mAdapter = new PanelSlicesAdapter(this, mPanel);
|
return mLayoutView;
|
||||||
|
}
|
||||||
|
|
||||||
mPanelSlices.setHasFixedSize(true);
|
private void loadAllSlices() {
|
||||||
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
|
mSliceLiveData.clear();
|
||||||
mPanelSlices.setAdapter(mAdapter);
|
final List<Uri> sliceUris = mPanel.getSlices();
|
||||||
|
mPanelSlicesLoaderCountdownLatch = new PanelSlicesLoaderCountdownLatch(sliceUris.size());
|
||||||
|
|
||||||
DividerItemDecoration itemDecoration = new DividerItemDecoration(getActivity());
|
for (Uri uri : sliceUris) {
|
||||||
itemDecoration.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH);
|
final LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(getActivity(), uri);
|
||||||
mPanelSlices.addItemDecoration(itemDecoration);
|
|
||||||
|
|
||||||
mTitleView.setText(mPanel.getTitle());
|
// Add slice first to make it in order. Will remove it later if there's an error.
|
||||||
|
mSliceLiveData.add(sliceLiveData);
|
||||||
|
|
||||||
mSeeMoreButton.setOnClickListener(getSeeMoreListener());
|
sliceLiveData.observe(getViewLifecycleOwner(), slice -> {
|
||||||
mDoneButton.setOnClickListener(getCloseListener());
|
// If the Slice has already loaded, do nothing.
|
||||||
|
if (mPanelSlicesLoaderCountdownLatch.isSliceLoaded(uri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//If getSeeMoreIntent() is null, hide the mSeeMoreButton.
|
/**
|
||||||
if (mPanel.getSeeMoreIntent() == null) {
|
* Watching for the {@link Slice} to load.
|
||||||
mSeeMoreButton.setVisibility(View.GONE);
|
* <p>
|
||||||
|
* If the Slice comes back {@code null} or with the Error attribute, remove the
|
||||||
|
* Slice data from the list, and mark the Slice as loaded.
|
||||||
|
* <p>
|
||||||
|
* If the Slice has come back fully loaded, then mark the Slice as loaded. No
|
||||||
|
* other actions required since we already have the Slice data in the list.
|
||||||
|
* <p>
|
||||||
|
* If the Slice does not match the above condition, we will still want to mark
|
||||||
|
* it as loaded after 250ms timeout to avoid delay showing up the panel for
|
||||||
|
* too long. Since we are still having the Slice data in the list, the Slice
|
||||||
|
* will show up later once it is loaded.
|
||||||
|
*/
|
||||||
|
final SliceMetadata metadata = SliceMetadata.from(getActivity(), slice);
|
||||||
|
if (slice == null || metadata.isErrorSlice()) {
|
||||||
|
mSliceLiveData.remove(sliceLiveData);
|
||||||
|
mPanelSlicesLoaderCountdownLatch.markSliceLoaded(uri);
|
||||||
|
} else if (metadata.getLoadingState() == SliceMetadata.LOADED_ALL) {
|
||||||
|
mPanelSlicesLoaderCountdownLatch.markSliceLoaded(uri);
|
||||||
|
} else {
|
||||||
|
Handler handler = new Handler();
|
||||||
|
handler.postDelayed(() -> {
|
||||||
|
mPanelSlicesLoaderCountdownLatch.markSliceLoaded(uri);
|
||||||
|
loadPanelWhenReady();
|
||||||
|
}, DURATION_SLICE_BINDING_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPanelWhenReady();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
/**
|
||||||
|
* When all of the Slices have loaded for the first time, then we can setup the
|
||||||
|
* {@link RecyclerView}.
|
||||||
|
* <p>
|
||||||
|
* When the Recyclerview has been laid out, we can begin the animation with the
|
||||||
|
* {@link mOnGlobalLayoutListener}, which calls {@link #animateIn()}.
|
||||||
|
*/
|
||||||
|
private void loadPanelWhenReady() {
|
||||||
|
if (mPanelSlicesLoaderCountdownLatch.isPanelReadyToLoad()) {
|
||||||
|
mAdapter = new PanelSlicesAdapter(
|
||||||
|
this, mSliceLiveData, mPanel.getMetricsCategory());
|
||||||
|
mPanelSlices.setAdapter(mAdapter);
|
||||||
|
mPanelSlices.getViewTreeObserver()
|
||||||
|
.addOnGlobalLayoutListener(mOnGlobalLayoutListener);
|
||||||
|
|
||||||
|
DividerItemDecoration itemDecoration = new DividerItemDecoration(getActivity());
|
||||||
|
itemDecoration
|
||||||
|
.setDividerCondition(DividerItemDecoration.DIVIDER_CONDITION_BOTH);
|
||||||
|
mPanelSlices.addItemDecoration(itemDecoration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate a Panel onto the screen.
|
||||||
|
* <p>
|
||||||
|
* Takes the entire panel and animates in from behind the navigation bar.
|
||||||
|
* <p>
|
||||||
|
* Relies on the Panel being having a fixed height to begin the animation.
|
||||||
|
*/
|
||||||
|
private void animateIn() {
|
||||||
|
final View panelContent = mLayoutView.findViewById(R.id.panel_container);
|
||||||
|
final AnimatorSet animatorSet = buildAnimatorSet(mLayoutView, panelContent.getHeight(),
|
||||||
|
0.0f, new DecelerateInterpolator());
|
||||||
|
final ValueAnimator animator = new ValueAnimator();
|
||||||
|
animator.setFloatValues(0.0f, 1.0f);
|
||||||
|
animatorSet.play(animator);
|
||||||
|
animatorSet.start();
|
||||||
|
// Remove the predraw listeners on the Panel.
|
||||||
|
mLayoutView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an {@link AnimatorSet} to bring the Panel, {@param parentView}in our out of the screen,
|
||||||
|
* based on the positional parameters {@param startY}, {@param endY} and at the rate set by the
|
||||||
|
* {@param interpolator}.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static AnimatorSet buildAnimatorSet(@NonNull View parentView, float startY, float endY,
|
||||||
|
@NonNull Interpolator interpolator) {
|
||||||
|
final View sheet = parentView.findViewById(R.id.panel_container);
|
||||||
|
final AnimatorSet animatorSet = new AnimatorSet();
|
||||||
|
animatorSet.setDuration(DURATION_ANIMATE_PANEL_MS);
|
||||||
|
animatorSet.setInterpolator(interpolator);
|
||||||
|
animatorSet.playTogether(ObjectAnimator.ofFloat(sheet, View.TRANSLATION_Y, startY, endY));
|
||||||
|
return animatorSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -20,7 +20,6 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDIC
|
|||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -30,13 +29,13 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.slice.Slice;
|
import androidx.slice.Slice;
|
||||||
import androidx.slice.widget.SliceLiveData;
|
|
||||||
import androidx.slice.widget.SliceView;
|
import androidx.slice.widget.SliceView;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.google.android.setupdesign.DividerItemDecoration;
|
import com.google.android.setupdesign.DividerItemDecoration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,14 +44,15 @@ import java.util.List;
|
|||||||
public class PanelSlicesAdapter
|
public class PanelSlicesAdapter
|
||||||
extends RecyclerView.Adapter<PanelSlicesAdapter.SliceRowViewHolder> {
|
extends RecyclerView.Adapter<PanelSlicesAdapter.SliceRowViewHolder> {
|
||||||
|
|
||||||
private final List<Uri> mSliceUris;
|
private final List<LiveData<Slice>> mSliceLiveData;
|
||||||
|
private final int mMetricsCategory;
|
||||||
private final PanelFragment mPanelFragment;
|
private final PanelFragment mPanelFragment;
|
||||||
private final PanelContent mPanelContent;
|
|
||||||
|
|
||||||
public PanelSlicesAdapter(PanelFragment fragment, PanelContent panel) {
|
public PanelSlicesAdapter(
|
||||||
|
PanelFragment fragment, List<LiveData<Slice>> sliceLiveData, int metricsCategory) {
|
||||||
mPanelFragment = fragment;
|
mPanelFragment = fragment;
|
||||||
mSliceUris = panel.getSlices();
|
mSliceLiveData = new ArrayList<>(sliceLiveData);
|
||||||
mPanelContent = panel;
|
mMetricsCategory = metricsCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -62,67 +62,60 @@ public class PanelSlicesAdapter
|
|||||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
final View view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false);
|
final View view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false);
|
||||||
|
|
||||||
return new SliceRowViewHolder(view, mPanelContent);
|
return new SliceRowViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
|
public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
|
||||||
sliceRowViewHolder.onBind(mPanelFragment, mSliceUris.get(position));
|
sliceRowViewHolder.onBind(mSliceLiveData.get(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return mSliceUris.size();
|
return mSliceLiveData.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
List<Uri> getData() {
|
List<LiveData<Slice>> getData() {
|
||||||
return mSliceUris;
|
return mSliceLiveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewHolder for binding Slices to SliceViews.
|
* ViewHolder for binding Slices to SliceViews.
|
||||||
*/
|
*/
|
||||||
public static class SliceRowViewHolder extends RecyclerView.ViewHolder
|
public class SliceRowViewHolder extends RecyclerView.ViewHolder
|
||||||
implements DividerItemDecoration.DividedViewHolder {
|
implements DividerItemDecoration.DividedViewHolder {
|
||||||
|
|
||||||
private final PanelContent mPanelContent;
|
|
||||||
|
|
||||||
private boolean mDividerAllowedAbove = true;
|
private boolean mDividerAllowedAbove = true;
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
LiveData<Slice> sliceLiveData;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
final SliceView sliceView;
|
final SliceView sliceView;
|
||||||
|
|
||||||
public SliceRowViewHolder(View view, PanelContent panelContent) {
|
public SliceRowViewHolder(View view) {
|
||||||
super(view);
|
super(view);
|
||||||
sliceView = view.findViewById(R.id.slice_view);
|
sliceView = view.findViewById(R.id.slice_view);
|
||||||
sliceView.setMode(SliceView.MODE_LARGE);
|
sliceView.setMode(SliceView.MODE_LARGE);
|
||||||
sliceView.showTitleItems(true);
|
sliceView.showTitleItems(true);
|
||||||
mPanelContent = panelContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBind(PanelFragment fragment, Uri sliceUri) {
|
public void onBind(LiveData<Slice> sliceLiveData) {
|
||||||
final Context context = sliceView.getContext();
|
sliceLiveData.observe(mPanelFragment.getViewLifecycleOwner(), sliceView);
|
||||||
sliceLiveData = SliceLiveData.fromUri(context, sliceUri);
|
|
||||||
sliceLiveData.observe(fragment.getViewLifecycleOwner(), sliceView);
|
|
||||||
|
|
||||||
// Do not show the divider above media devices switcher slice per request
|
// Do not show the divider above media devices switcher slice per request
|
||||||
if (sliceUri.equals(MEDIA_OUTPUT_INDICATOR_SLICE_URI)) {
|
final Slice slice = sliceLiveData.getValue();
|
||||||
|
if (slice != null && slice.getUri().equals(MEDIA_OUTPUT_INDICATOR_SLICE_URI)) {
|
||||||
mDividerAllowedAbove = false;
|
mDividerAllowedAbove = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log Panel interaction
|
// Log Panel interaction
|
||||||
sliceView.setOnSliceActionListener(
|
sliceView.setOnSliceActionListener(
|
||||||
((eventInfo, sliceItem) -> {
|
((eventInfo, sliceItem) -> {
|
||||||
FeatureFactory.getFactory(context)
|
FeatureFactory.getFactory(sliceView.getContext())
|
||||||
.getMetricsFeatureProvider()
|
.getMetricsFeatureProvider()
|
||||||
.action(0 /* attribution */,
|
.action(0 /* attribution */,
|
||||||
SettingsEnums.ACTION_PANEL_INTERACTION,
|
SettingsEnums.ACTION_PANEL_INTERACTION,
|
||||||
mPanelContent.getMetricsCategory(),
|
mMetricsCategory,
|
||||||
sliceUri.toString() /* log key */,
|
sliceLiveData.toString() /* log key */,
|
||||||
eventInfo.actionType /* value */);
|
eventInfo.actionType /* value */);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.slice.Slice;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to isolate the work tracking all of the {@link Slice Slices} being loaded.
|
||||||
|
* <p>
|
||||||
|
* Uses a {@link CountDownLatch} and a {@link Set} of Slices to track how many
|
||||||
|
* Slices have been loaded. A Slice can only be counted as being loaded a single time, even
|
||||||
|
* when they get updated later.
|
||||||
|
* <p>
|
||||||
|
* To use the class, pass the number of expected Slices to load into the constructor. For
|
||||||
|
* every Slice that loads, call {@link #markSliceLoaded(Uri)} with the corresponding
|
||||||
|
* {@link Uri}. Then check if all of the Slices have loaded with
|
||||||
|
* {@link #isPanelReadyToLoad()}, which will return {@code true} the first time after all
|
||||||
|
* Slices have loaded.
|
||||||
|
*/
|
||||||
|
public class PanelSlicesLoaderCountdownLatch {
|
||||||
|
private final Set<Uri> mLoadedSlices;
|
||||||
|
private final CountDownLatch mCountDownLatch;
|
||||||
|
private boolean slicesReadyToLoad = false;
|
||||||
|
|
||||||
|
public PanelSlicesLoaderCountdownLatch(int countdownSize) {
|
||||||
|
mLoadedSlices = new HashSet<>();
|
||||||
|
mCountDownLatch = new CountDownLatch(countdownSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the {@param sliceUri} has been loaded: if not, then decrement the countdown
|
||||||
|
* latch, and if so, then do nothing.
|
||||||
|
*/
|
||||||
|
public void markSliceLoaded(Uri sliceUri) {
|
||||||
|
if (mLoadedSlices.contains(sliceUri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mLoadedSlices.add(sliceUri);
|
||||||
|
mCountDownLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the Slice has already been loaded.
|
||||||
|
*/
|
||||||
|
public boolean isSliceLoaded(Uri uri) {
|
||||||
|
return mLoadedSlices.contains(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} when all Slices have loaded, and the Panel has not yet been loaded.
|
||||||
|
*/
|
||||||
|
public boolean isPanelReadyToLoad() {
|
||||||
|
/**
|
||||||
|
* Use {@link slicesReadyToLoad} to track whether or not the Panel has been loaded. We
|
||||||
|
* only want to animate the Panel a single time.
|
||||||
|
*/
|
||||||
|
if ((mCountDownLatch.getCount() == 0) && !slicesReadyToLoad) {
|
||||||
|
slicesReadyToLoad = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -81,12 +82,16 @@ public class PanelFragmentTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onCreateView_adapterGetsDataset() {
|
public void onCreateView_countdownLatch_setup() {
|
||||||
mPanelFragment.onCreateView(LayoutInflater.from(mContext),
|
mPanelFragment.onCreateView(LayoutInflater.from(mContext),
|
||||||
new LinearLayout(mContext), null);
|
new LinearLayout(mContext), null);
|
||||||
PanelSlicesAdapter adapter = mPanelFragment.mAdapter;
|
PanelSlicesLoaderCountdownLatch countdownLatch =
|
||||||
|
mPanelFragment.mPanelSlicesLoaderCountdownLatch;
|
||||||
|
for (Uri sliecUri: mFakePanelContent.getSlices()) {
|
||||||
|
countdownLatch.markSliceLoaded(sliecUri);
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(adapter.getData()).containsAllIn(mFakePanelContent.getSlices());
|
assertThat(countdownLatch.isPanelReadyToLoad()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -23,41 +23,53 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.slice.Slice;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.slices.CustomSliceRegistry;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
import com.android.settings.testutils.FakeFeatureFactory;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.Robolectric;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.Robolectric;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
import org.robolectric.android.controller.ActivityController;
|
import org.robolectric.android.controller.ActivityController;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class PanelSlicesAdapterTest {
|
public class PanelSlicesAdapterTest {
|
||||||
|
|
||||||
|
private static final Uri DATA_URI = CustomSliceRegistry.DATA_USAGE_SLICE_URI;
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private PanelFragment mPanelFragment;
|
private PanelFragment mPanelFragment;
|
||||||
private FakePanelContent mFakePanelContent;
|
|
||||||
private FakeFeatureFactory mFakeFeatureFactory;
|
|
||||||
private PanelFeatureProvider mPanelFeatureProvider;
|
private PanelFeatureProvider mPanelFeatureProvider;
|
||||||
|
private FakeFeatureFactory mFakeFeatureFactory;
|
||||||
|
private FakePanelContent mFakePanelContent;
|
||||||
|
private List<LiveData<Slice>> mData = new ArrayList<>();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LiveData<Slice> mLiveData;
|
||||||
|
|
||||||
|
private Slice mSlice;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = RuntimeEnvironment.application;
|
||||||
|
|
||||||
mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
|
mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
|
||||||
@@ -76,12 +88,22 @@ public class PanelSlicesAdapterTest {
|
|||||||
.get()
|
.get()
|
||||||
.getSupportFragmentManager()
|
.getSupportFragmentManager()
|
||||||
.findFragmentById(R.id.main_content));
|
.findFragmentById(R.id.main_content));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void constructTestLiveData(Uri uri) {
|
||||||
|
// Create a slice to return for the LiveData
|
||||||
|
mSlice = spy(new Slice());
|
||||||
|
doReturn(uri).when(mSlice).getUri();
|
||||||
|
when(mLiveData.getValue()).thenReturn(mSlice);
|
||||||
|
mData.add(mLiveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onCreateViewHolder_returnsSliceRowViewHolder() {
|
public void onCreateViewHolder_returnsSliceRowViewHolder() {
|
||||||
|
constructTestLiveData(DATA_URI);
|
||||||
final PanelSlicesAdapter adapter =
|
final PanelSlicesAdapter adapter =
|
||||||
new PanelSlicesAdapter(mPanelFragment, mFakePanelContent);
|
new PanelSlicesAdapter(mPanelFragment, mData, 0 /* metrics category */);
|
||||||
final ViewGroup view = new FrameLayout(mContext);
|
final ViewGroup view = new FrameLayout(mContext);
|
||||||
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
||||||
adapter.onCreateViewHolder(view, 0);
|
adapter.onCreateViewHolder(view, 0);
|
||||||
@@ -89,24 +111,11 @@ public class PanelSlicesAdapterTest {
|
|||||||
assertThat(viewHolder.sliceView).isNotNull();
|
assertThat(viewHolder.sliceView).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onBindViewHolder_bindsSlice() {
|
|
||||||
final PanelSlicesAdapter adapter =
|
|
||||||
new PanelSlicesAdapter(mPanelFragment, mFakePanelContent);
|
|
||||||
final int position = 0;
|
|
||||||
final ViewGroup view = new FrameLayout(mContext);
|
|
||||||
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
|
||||||
adapter.onCreateViewHolder(view, 0 /* view type*/);
|
|
||||||
|
|
||||||
adapter.onBindViewHolder(viewHolder, position);
|
|
||||||
|
|
||||||
assertThat(viewHolder.sliceLiveData).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nonMediaOutputIndicatorSlice_shouldAllowDividerAboveAndBelow() {
|
public void nonMediaOutputIndicatorSlice_shouldAllowDividerAboveAndBelow() {
|
||||||
|
constructTestLiveData(DATA_URI);
|
||||||
final PanelSlicesAdapter adapter =
|
final PanelSlicesAdapter adapter =
|
||||||
new PanelSlicesAdapter(mPanelFragment, mFakePanelContent);
|
new PanelSlicesAdapter(mPanelFragment, mData, 0 /* metrics category */);
|
||||||
final int position = 0;
|
final int position = 0;
|
||||||
final ViewGroup view = new FrameLayout(mContext);
|
final ViewGroup view = new FrameLayout(mContext);
|
||||||
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
||||||
@@ -120,32 +129,10 @@ public class PanelSlicesAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mediaOutputIndicatorSlice_shouldNotAllowDividerAbove() {
|
public void mediaOutputIndicatorSlice_shouldNotAllowDividerAbove() {
|
||||||
PanelContent mediaOutputIndicatorSlicePanelContent = new PanelContent() {
|
constructTestLiveData(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
|
||||||
@Override
|
|
||||||
public CharSequence getTitle() {
|
|
||||||
return "title";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Uri> getSlices() {
|
|
||||||
return Arrays.asList(
|
|
||||||
MEDIA_OUTPUT_INDICATOR_SLICE_URI
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getSeeMoreIntent() {
|
|
||||||
return new Intent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMetricsCategory() {
|
|
||||||
return SettingsEnums.TESTING;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
final PanelSlicesAdapter adapter =
|
final PanelSlicesAdapter adapter =
|
||||||
new PanelSlicesAdapter(mPanelFragment, mediaOutputIndicatorSlicePanelContent);
|
new PanelSlicesAdapter(mPanelFragment, mData, 0 /* metrics category */);
|
||||||
final int position = 0;
|
final int position = 0;
|
||||||
final ViewGroup view = new FrameLayout(mContext);
|
final ViewGroup view = new FrameLayout(mContext);
|
||||||
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
final PanelSlicesAdapter.SliceRowViewHolder viewHolder =
|
||||||
|
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class PanelSlicesLoaderCountdownLatchTest {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private PanelSlicesLoaderCountdownLatch mSliceCountdownLatch;
|
||||||
|
|
||||||
|
private static final Uri[] URIS = new Uri[] {
|
||||||
|
Uri.parse("content://testUri"),
|
||||||
|
Uri.parse("content://wowUri"),
|
||||||
|
Uri.parse("content://boxTurtle")
|
||||||
|
};
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mSliceCountdownLatch = new PanelSlicesLoaderCountdownLatch(URIS.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasLoaded_newObject_returnsFalse() {
|
||||||
|
assertThat(mSliceCountdownLatch.isSliceLoaded(URIS[0])).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasLoaded_markSliceLoaded_returnsTrue() {
|
||||||
|
mSliceCountdownLatch.markSliceLoaded(URIS[0]);
|
||||||
|
|
||||||
|
assertThat(mSliceCountdownLatch.isSliceLoaded(URIS[0])).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void markSliceLoaded_onlyCountsDownUniqueUris() {
|
||||||
|
for (int i = 0; i < URIS.length; i++) {
|
||||||
|
mSliceCountdownLatch.markSliceLoaded(URIS[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(mSliceCountdownLatch.isPanelReadyToLoad()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void areSlicesReadyToLoad_allSlicesLoaded_returnsTrue() {
|
||||||
|
for (int i = 0; i < URIS.length; i++) {
|
||||||
|
mSliceCountdownLatch.markSliceLoaded(URIS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(mSliceCountdownLatch.isPanelReadyToLoad()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void areSlicesReadyToLoad_onlyReturnsTrueOnce() {
|
||||||
|
for (int i = 0; i < URIS.length; i++) {
|
||||||
|
mSliceCountdownLatch.markSliceLoaded(URIS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that it returns true once
|
||||||
|
assertThat(mSliceCountdownLatch.isPanelReadyToLoad()).isTrue();
|
||||||
|
// Verify the second call returns false without external state change
|
||||||
|
assertThat(mSliceCountdownLatch.isPanelReadyToLoad()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user