package com.android.settings.panel;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public Intent getSeeMoreIntent() {
return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
}
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PANEL_INTERNET_CONNECTIVITY;
+ }
}
import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public Intent getSeeMoreIntent() {
return null;
}
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PANEL_MEDIA_OUTPUT;
+ }
}
intent.setClassName(mContext.getPackageName(), SubSettings.class.getName());
return intent;
}
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PANEL_NFC;
+ }
}
import android.content.Intent;
import android.net.Uri;
+import com.android.settingslib.core.instrumentation.Instrumentable;
+
import java.util.List;
/**
* Represents the data class needed to create a Settings Panel. See {@link PanelFragment}.
*/
-public interface PanelContent {
+public interface PanelContent extends Instrumentable {
/**
* @return a string for the title of the Panel.
public interface PanelFeatureProvider {
/**
- * Returns {@link PanelContent} as specified by the {@code panelType} and {@code packageName}.
+ * Returns {@link PanelContent} as specified by the {@param panelType}, and
+ * {@param mediaPackageName}.
*/
- PanelContent getPanel(Context context, String panelType, String packageName);
+ PanelContent getPanel(Context context, String panelType, String mediaPackageName);
}
public class PanelFeatureProviderImpl implements PanelFeatureProvider {
@Override
- public PanelContent getPanel(Context context, String panelType, String packageName) {
+ public PanelContent getPanel(Context context, String panelType, String mediaPackageName) {
switch (panelType) {
case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
return InternetConnectivityPanel.create(context);
case Settings.Panel.ACTION_NFC:
return NfcPanel.create(context);
case ACTION_MEDIA_OUTPUT:
- return MediaOutputPanel.create(context, packageName);
+ return MediaOutputPanel.create(context, mediaPackageName);
}
throw new IllegalStateException("No matching panel for: " + panelType);
package com.android.settings.panel;
-import android.content.Intent;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.panel.PanelLoggingContract.PanelClosedKeys;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
public class PanelFragment extends Fragment {
private Button mDoneButton;
private RecyclerView mPanelSlices;
+ private PanelContent mPanel;
+ private final MetricsFeatureProvider mMetricsProvider;
+
@VisibleForTesting
PanelSlicesAdapter mAdapter;
- private View.OnClickListener mDoneButtonListener = (v) -> {
- Log.d(TAG, "Closing dialog");
- getActivity().finish();
- };
-
public PanelFragment() {
+ final Context context = getActivity();
+ mMetricsProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
}
@Nullable
mTitleView = view.findViewById(R.id.panel_title);
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)
+ final String panelType =
+ arguments.getString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT);
+ final String callingPackageName =
+ arguments.getString(SettingsPanelActivity.KEY_CALLING_PACKAGE_NAME);
+ final String mediaPackageName =
+ arguments.getString(SettingsPanelActivity.KEY_MEDIA_PACKAGE_NAME);
+
+ // TODO (b/124399577) transform interface to take a context and bundle.
+ mPanel = FeatureFactory.getFactory(activity)
.getPanelFeatureProvider()
- .getPanel(activity, panelType, packageName);
+ .getPanel(activity, panelType, mediaPackageName);
- mAdapter = new PanelSlicesAdapter(this, panel.getSlices());
+ // Log panel opened.
+ mMetricsProvider.action(
+ 0 /* attribution */,
+ SettingsEnums.PAGE_VISIBLE /* opened panel - Action */,
+ mPanel.getMetricsCategory(),
+ callingPackageName,
+ 0 /* value */);
+
+ mAdapter = new PanelSlicesAdapter(this, mPanel);
mPanelSlices.setHasFixedSize(true);
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
mPanelSlices.setAdapter(mAdapter);
- mTitleView.setText(panel.getTitle());
+ mTitleView.setText(mPanel.getTitle());
- mSeeMoreButton.setOnClickListener(getSeeMoreListener(panel.getSeeMoreIntent()));
- mDoneButton.setOnClickListener(mDoneButtonListener);
+ mSeeMoreButton.setOnClickListener(getSeeMoreListener());
+ mDoneButton.setOnClickListener(getCloseListener());
//If getSeeMoreIntent() is null, hide the mSeeMoreButton.
- if (panel.getSeeMoreIntent() == null) {
+ if (mPanel.getSeeMoreIntent() == null) {
mSeeMoreButton.setVisibility(View.GONE);
}
return view;
}
- private View.OnClickListener getSeeMoreListener(final Intent intent) {
+ @VisibleForTesting
+ View.OnClickListener getSeeMoreListener() {
return (v) -> {
+ mMetricsProvider.action(
+ 0 /* attribution */,
+ SettingsEnums.PAGE_HIDE ,
+ mPanel.getMetricsCategory(),
+ PanelClosedKeys.KEY_SEE_MORE,
+ 0 /* value */);
final FragmentActivity activity = getActivity();
- activity.startActivity(intent);
+ activity.startActivityForResult(mPanel.getSeeMoreIntent(), 0);
activity.finish();
};
}
+
+ @VisibleForTesting
+ View.OnClickListener getCloseListener() {
+ return (v) -> {
+ mMetricsProvider.action(
+ 0 /* attribution */,
+ SettingsEnums.PAGE_HIDE,
+ mPanel.getMetricsCategory(),
+ PanelClosedKeys.KEY_DONE,
+ 0 /* value */);
+ getActivity().finish();
+ };
+ }
}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Simple contract class to track keys in Panel logging.
+ *
+ * <p>
+ * Constants should only be removed if underlying panel, or use case is removed.
+ * </p>
+ */
+public class PanelLoggingContract {
+
+ /**
+ * Keys tracking different ways users exit Panels.
+ */
+ interface PanelClosedKeys {
+ /**
+ * The user clicked the See More button linking deeper into Settings.
+ */
+ String KEY_SEE_MORE = "see_more";
+
+ /**
+ * The user clicked the Done button, closing the Panel.
+ */
+ String KEY_DONE = "done";
+
+ /**
+ * The user clicked outside the dialog, closing the Panel.
+ */
+ String KEY_CLICKED_OUT = "clicked_out";
+ }
+}
package com.android.settings.panel;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
import androidx.slice.widget.SliceView;
import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
-import java.util.ArrayList;
import java.util.List;
/**
private final List<Uri> mSliceUris;
private final PanelFragment mPanelFragment;
+ private final PanelContent mPanelContent;
- public PanelSlicesAdapter(PanelFragment fragment, List<Uri> sliceUris) {
+ public PanelSlicesAdapter(PanelFragment fragment, PanelContent panel) {
mPanelFragment = fragment;
- mSliceUris = new ArrayList<>(sliceUris);
+ mSliceUris = panel.getSlices();
+ mPanelContent = panel;
}
@NonNull
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false);
- return new SliceRowViewHolder(view);
+ return new SliceRowViewHolder(view, mPanelContent);
}
@Override
*/
public static class SliceRowViewHolder extends RecyclerView.ViewHolder {
+ private final PanelContent mPanelContent;
+
@VisibleForTesting
LiveData<Slice> sliceLiveData;
@VisibleForTesting
final SliceView sliceView;
- public SliceRowViewHolder(View view) {
+ public SliceRowViewHolder(View view, PanelContent panelContent) {
super(view);
sliceView = view.findViewById(R.id.slice_view);
sliceView.setMode(SliceView.MODE_LARGE);
+ mPanelContent = panelContent;
}
public void onBind(PanelFragment fragment, Uri sliceUri) {
final Context context = sliceView.getContext();
sliceLiveData = SliceLiveData.fromUri(context, sliceUri);
sliceLiveData.observe(fragment.getViewLifecycleOwner(), sliceView);
+
+ // Log Panel interaction
+ sliceView.setOnSliceActionListener(
+ ((eventInfo, sliceItem) -> {
+ FeatureFactory.getFactory(context)
+ .getMetricsFeatureProvider()
+ .action(0 /* attribution */,
+ SettingsEnums.ACTION_PANEL_INTERACTION,
+ mPanelContent.getMetricsCategory(),
+ sliceUri.toString() /* log key */,
+ eventInfo.actionType /* value */);
+ })
+ );
}
}
}
import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT;
import static com.android.settingslib.media.MediaOutputSliceConstants.EXTRA_PACKAGE_NAME;
+import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.panel.PanelLoggingContract.PanelClosedKeys;
/**
* Dialog Activity to host Settings Slices.
* 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";
+
+ /**
+ * Key specifying the package which requested the Panel.
+ */
+ public static final String KEY_CALLING_PACKAGE_NAME = "PANEL_CALLING_PACKAGE_NAME";
+
+ /**
+ * Key specifying the package name for which the
+ */
+ public static final String KEY_MEDIA_PACKAGE_NAME = "PANEL_MEDIA_PACKAGE_NAME";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
return;
}
- final String packageName =
+ final String mediaPackageName =
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");
+ && TextUtils.isEmpty(mediaPackageName)) {
+ Log.e(TAG, "Missing EXTRA_PACKAGE_NAME, closing Panel Activity");
finish();
return;
}
WindowManager.LayoutParams.WRAP_CONTENT);
mBundle.putString(KEY_PANEL_TYPE_ARGUMENT, callingIntent.getAction());
- mBundle.putString(KEY_PANEL_PACKAGE_NAME, packageName);
+ mBundle.putString(KEY_CALLING_PACKAGE_NAME, getCallingPackage());
+ mBundle.putString(KEY_MEDIA_PACKAGE_NAME, mediaPackageName);
final PanelFragment panelFragment = new PanelFragment();
panelFragment.setArguments(mBundle);
fragmentManager.beginTransaction().add(R.id.main_content, panelFragment).commit();
}
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ final PanelContent panelContent = FeatureFactory.getFactory(this)
+ .getPanelFeatureProvider()
+ .getPanel(this, getIntent().getAction(), null /* Media Package Name */);
+ FeatureFactory.getFactory(this)
+ .getMetricsFeatureProvider()
+ .action(0 /* attribution */,
+ SettingsEnums.PAGE_HIDE,
+ panelContent.getMetricsCategory(),
+ PanelClosedKeys.KEY_CLICKED_OUT,
+ 0 /* value */);
+ }
+ return super.onTouchEvent(event);
+ }
}
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public Intent getSeeMoreIntent() {
return new Intent(Settings.ACTION_SOUND_SETTINGS);
}
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PANEL_VOLUME;
+ }
}
\ No newline at end of file
import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI;
+import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.net.Uri;
public Intent getSeeMoreIntent() {
return INTENT;
}
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.TESTING;
+ }
}
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
+import android.view.View;
import android.widget.LinearLayout;
import com.android.settings.R;
.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();
+ }
+
+ @Test
+ public void onCreateView_adapterGetsDataset() {
mPanelFragment.onCreateView(LayoutInflater.from(mContext),
new LinearLayout(mContext), null);
PanelSlicesAdapter adapter = mPanelFragment.mAdapter;
assertThat(adapter.getData()).containsAllIn(mFakePanelContent.getSlices());
}
+ @Test
+ public void onCreate_logsOpenEvent() {
+ verify(mFakeFeatureFactory.metricsFeatureProvider).action(
+ 0,
+ SettingsEnums.PAGE_VISIBLE,
+ mFakePanelContent.getMetricsCategory(),
+ null,
+ 0);
+ }
+
+ @Test
+ public void panelSeeMoreClick_logsCloseEvent() {
+ final View.OnClickListener listener = mPanelFragment.getSeeMoreListener();
+
+ listener.onClick(null);
+
+ verify(mFakeFeatureFactory.metricsFeatureProvider).action(
+ 0,
+ SettingsEnums.PAGE_HIDE,
+ mFakePanelContent.getMetricsCategory(),
+ PanelLoggingContract.PanelClosedKeys.KEY_SEE_MORE,
+ 0
+ );
+ }
+
+ @Test
+ public void panelDoneClick_logsCloseEvent() {
+ final View.OnClickListener listener = mPanelFragment.getCloseListener();
+
+ listener.onClick(null);
+
+ verify(mFakeFeatureFactory.metricsFeatureProvider).action(
+ 0,
+ SettingsEnums.PAGE_HIDE,
+ mFakePanelContent.getMetricsCategory(),
+ PanelLoggingContract.PanelClosedKeys.KEY_DONE,
+ 0
+ );
+ }
}
\ No newline at end of file
.getSupportFragmentManager()
.findFragmentById(R.id.main_content));
- mAdapter = new PanelSlicesAdapter(mPanelFragment, mFakePanelContent.getSlices());
+ mAdapter = new PanelSlicesAdapter(mPanelFragment, mFakePanelContent);
}
@Test
package com.android.settings.panel;
-import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_PACKAGE_NAME;
+import static com.android.settings.panel.SettingsPanelActivity.KEY_MEDIA_PACKAGE_NAME;
import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT;
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.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
import android.content.Intent;
+import android.view.MotionEvent;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@RunWith(RobolectricTestRunner.class)
public class SettingsPanelActivityTest {
+ private FakeFeatureFactory mFakeFeatureFactory;
+ private FakeSettingsPanelActivity mSettingsPanelActivity;
+ private PanelFeatureProvider mPanelFeatureProvider;
+ private FakePanelContent mFakePanelContent;
+
+ @Before
+ public void setUp() {
+ mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+ mSettingsPanelActivity = Robolectric.buildActivity(FakeSettingsPanelActivity.class)
+ .create().get();
+ mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
+ mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider;
+ mFakePanelContent = new FakePanelContent();
+ doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any(), any());
+ }
+
@Test
public void startMediaOutputSlice_withPackageName_bundleShouldHaveValue() {
final Intent intent = new Intent()
final SettingsPanelActivity activity =
Robolectric.buildActivity(SettingsPanelActivity.class, intent).create().get();
- assertThat(activity.mBundle.getString(KEY_PANEL_PACKAGE_NAME))
+ assertThat(activity.mBundle.getString(KEY_MEDIA_PACKAGE_NAME))
.isEqualTo("com.google.android.music");
assertThat(activity.mBundle.getString(KEY_PANEL_TYPE_ARGUMENT))
.isEqualTo("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_MEDIA_PACKAGE_NAME)).isFalse();
assertThat(activity.mBundle.containsKey(KEY_PANEL_TYPE_ARGUMENT)).isFalse();
}
+
+ @Test
+ public void onTouchEvent_outsideAction_logsPanelClosed() {
+ final MotionEvent event = mock(MotionEvent.class);
+ when(event.getAction()).thenReturn(MotionEvent.ACTION_OUTSIDE);
+
+ mSettingsPanelActivity.onTouchEvent(event);
+
+ verify(mFakeFeatureFactory.metricsFeatureProvider).action(
+ 0,
+ SettingsEnums.PAGE_HIDE,
+ SettingsEnums.TESTING,
+ PanelLoggingContract.PanelClosedKeys.KEY_CLICKED_OUT,
+ 0
+ );
+ }
}