Hide notification channel slice that is interacted
- Ignore interacted package in ContextualNotificationChannelSlice. - Regularly clear package record with background worker. Bug:129726858 Test: visual, robotests Change-Id: I94661a53bcbbe4a15479224c33cfb2eff345aa67
This commit is contained in:
@@ -16,14 +16,23 @@
|
|||||||
|
|
||||||
package com.android.settings.homepage.contextualcards.slices;
|
package com.android.settings.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.slices.CustomSliceRegistry;
|
import com.android.settings.slices.CustomSliceRegistry;
|
||||||
|
import com.android.settings.slices.SliceBackgroundWorker;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class ContextualNotificationChannelSlice extends NotificationChannelSlice {
|
public class ContextualNotificationChannelSlice extends NotificationChannelSlice {
|
||||||
|
|
||||||
|
public static final String PREFS = "notification_channel_slice_prefs";
|
||||||
|
public static final String PREF_KEY_INTERACTED_PACKAGES = "interacted_packages";
|
||||||
|
|
||||||
public ContextualNotificationChannelSlice(Context context) {
|
public ContextualNotificationChannelSlice(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -37,4 +46,18 @@ public class ContextualNotificationChannelSlice extends NotificationChannelSlice
|
|||||||
protected CharSequence getSubTitle(String packageName, int uid) {
|
protected CharSequence getSubTitle(String packageName, int uid) {
|
||||||
return mContext.getText(R.string.recently_installed_app);
|
return mContext.getText(R.string.recently_installed_app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isUserInteracted(String packageName) {
|
||||||
|
// Check the package has been interacted on current slice or not.
|
||||||
|
final Set<String> interactedPackages =
|
||||||
|
mContext.getSharedPreferences(PREFS, MODE_PRIVATE)
|
||||||
|
.getStringSet(PREF_KEY_INTERACTED_PACKAGES, new ArraySet<>());
|
||||||
|
return interactedPackages.contains(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends SliceBackgroundWorker> getBackgroundWorkerClass() {
|
||||||
|
return NotificationChannelWorker.class;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -218,6 +218,17 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
.toIntent();
|
.toIntent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the package has been interacted by user or not.
|
||||||
|
* Will use to filter package in {@link #getRecentlyInstalledPackages()}.
|
||||||
|
*
|
||||||
|
* @param packageName The app package name.
|
||||||
|
* @return true if the package was interacted, false otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isUserInteracted(String packageName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
IconCompat getApplicationIcon(String packageName) {
|
IconCompat getApplicationIcon(String packageName) {
|
||||||
final Drawable drawable;
|
final Drawable drawable;
|
||||||
@@ -328,8 +339,9 @@ public class NotificationChannelSlice implements CustomSliceable {
|
|||||||
final List<PackageInfo> installedPackages =
|
final List<PackageInfo> installedPackages =
|
||||||
mContext.getPackageManager().getInstalledPackages(0);
|
mContext.getPackageManager().getInstalledPackages(0);
|
||||||
for (PackageInfo packageInfo : installedPackages) {
|
for (PackageInfo packageInfo : installedPackages) {
|
||||||
// Not include system app.
|
// Not include system app and interacted app.
|
||||||
if (packageInfo.applicationInfo.isSystemApp()) {
|
if (packageInfo.applicationInfo.isSystemApp()
|
||||||
|
|| isUserInteracted(packageInfo.packageName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
|
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREFS;
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREF_KEY_INTERACTED_PACKAGES;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
import com.android.settings.slices.SliceBackgroundWorker;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class NotificationChannelWorker extends SliceBackgroundWorker<Void> {
|
||||||
|
|
||||||
|
public NotificationChannelWorker(Context context, Uri uri) {
|
||||||
|
super(context, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSlicePinned() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSliceUnpinned() {
|
||||||
|
removeUninstalledPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeUninstalledPackages() {
|
||||||
|
final SharedPreferences prefs = getContext().getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||||
|
final Set<String> interactedPackages =
|
||||||
|
prefs.getStringSet(PREF_KEY_INTERACTED_PACKAGES, new ArraySet());
|
||||||
|
if (interactedPackages.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PackageInfo> installedPackageInfos =
|
||||||
|
getContext().getPackageManager().getInstalledPackages(0);
|
||||||
|
final List<String> installedPackages = installedPackageInfos.stream()
|
||||||
|
.map(packageInfo -> packageInfo.packageName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
final Set<String> newInteractedPackages = new ArraySet<>();
|
||||||
|
for (String packageName : interactedPackages) {
|
||||||
|
if (installedPackages.contains(packageName)) {
|
||||||
|
newInteractedPackages.add(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefs.edit().putStringSet(PREF_KEY_INTERACTED_PACKAGES, newInteractedPackages).apply();
|
||||||
|
}
|
||||||
|
}
|
@@ -16,30 +16,49 @@
|
|||||||
|
|
||||||
package com.android.settings.homepage.contextualcards.slices;
|
package com.android.settings.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
|
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREFS;
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREF_KEY_INTERACTED_PACKAGES;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.slices.CustomSliceRegistry;
|
import com.android.settings.slices.CustomSliceRegistry;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class ContextualNotificationChannelSliceTest {
|
public class ContextualNotificationChannelSliceTest {
|
||||||
|
|
||||||
|
private static final String PACKAGE_NAME = "package_name";
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private ContextualNotificationChannelSlice mNotificationChannelSlice;
|
private ContextualNotificationChannelSlice mNotificationChannelSlice;
|
||||||
|
private SharedPreferences mSharedPreferences;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = RuntimeEnvironment.application;
|
||||||
mNotificationChannelSlice = new ContextualNotificationChannelSlice(mContext);
|
mNotificationChannelSlice = new ContextualNotificationChannelSlice(mContext);
|
||||||
|
mSharedPreferences = mContext.getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
removeInteractedPackageFromSharedPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -55,4 +74,34 @@ public class ContextualNotificationChannelSliceTest {
|
|||||||
|
|
||||||
assertThat(subTitle).isEqualTo(mContext.getText(R.string.recently_installed_app));
|
assertThat(subTitle).isEqualTo(mContext.getText(R.string.recently_installed_app));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isUserInteracted_hasInteractedPackage_shouldBeTrue() {
|
||||||
|
addInteractedPackageToSharedPreference();
|
||||||
|
|
||||||
|
final boolean isInteracted = mNotificationChannelSlice.isUserInteracted(PACKAGE_NAME);
|
||||||
|
|
||||||
|
assertThat(isInteracted).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isUserInteracted_noInteractedPackage_shouldBeFalse() {
|
||||||
|
final boolean isInteracted = mNotificationChannelSlice.isUserInteracted(PACKAGE_NAME);
|
||||||
|
|
||||||
|
assertThat(isInteracted).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInteractedPackageToSharedPreference() {
|
||||||
|
final Set<String> interactedPackages = new ArraySet<>();
|
||||||
|
interactedPackages.add(PACKAGE_NAME);
|
||||||
|
|
||||||
|
mSharedPreferences.edit().putStringSet(PREF_KEY_INTERACTED_PACKAGES,
|
||||||
|
interactedPackages).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInteractedPackageFromSharedPreference() {
|
||||||
|
if (mSharedPreferences.contains(PREF_KEY_INTERACTED_PACKAGES)) {
|
||||||
|
mSharedPreferences.edit().remove(PREF_KEY_INTERACTED_PACKAGES).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@ 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 static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
@@ -299,6 +298,21 @@ public class NotificationChannelSliceTest {
|
|||||||
assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.no_suggested_app));
|
assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.no_suggested_app));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(shadows = ShadowRestrictedLockUtilsInternal.class)
|
||||||
|
public void getSlice_isInteractedPackage_shouldHaveNoSuggestedAppTitle() {
|
||||||
|
addMockPackageToPackageManager(true /* isRecentlyInstalled */,
|
||||||
|
ApplicationInfo.FLAG_INSTALLED);
|
||||||
|
mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */,
|
||||||
|
false /* isChannelBlocked */);
|
||||||
|
doReturn(true).when(mNotificationChannelSlice).isUserInteracted(any(String.class));
|
||||||
|
|
||||||
|
final Slice slice = mNotificationChannelSlice.getSlice();
|
||||||
|
|
||||||
|
final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
|
||||||
|
assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.no_suggested_app));
|
||||||
|
}
|
||||||
|
|
||||||
private void addMockPackageToPackageManager(boolean isRecentlyInstalled, int flags) {
|
private void addMockPackageToPackageManager(boolean isRecentlyInstalled, int flags) {
|
||||||
final ApplicationInfo applicationInfo = new ApplicationInfo();
|
final ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||||
applicationInfo.name = APP_LABEL;
|
applicationInfo.name = APP_LABEL;
|
||||||
|
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.homepage.contextualcards.slices;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
|
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREFS;
|
||||||
|
import static com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice.PREF_KEY_INTERACTED_PACKAGES;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.shadows.ShadowPackageManager;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class NotificationChannelWorkerTest {
|
||||||
|
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
||||||
|
private static final String PACKAGE_NAME = "com.test.notification.channel.slice";
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private NotificationChannelWorker mNotificationChannelWorker;
|
||||||
|
private ShadowPackageManager mPackageManager;
|
||||||
|
private SharedPreferences mSharedPreferences;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mNotificationChannelWorker = new NotificationChannelWorker(mContext, URI);
|
||||||
|
|
||||||
|
// Shadow PackageManager to add mock package.
|
||||||
|
mPackageManager = shadowOf(mContext.getPackageManager());
|
||||||
|
|
||||||
|
mSharedPreferences = mContext.getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||||
|
addInteractedPackageToSharedPreference();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
mPackageManager.removePackage(PACKAGE_NAME);
|
||||||
|
removeInteractedPackageFromSharedPreference();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onSliceUnpinned_interactedPackageIsUninstalled_shouldRemovePackage() {
|
||||||
|
mNotificationChannelWorker.onSliceUnpinned();
|
||||||
|
|
||||||
|
final Set<String> interactedPackages = mSharedPreferences.getStringSet(
|
||||||
|
PREF_KEY_INTERACTED_PACKAGES, new ArraySet<>());
|
||||||
|
assertThat(interactedPackages.contains(PACKAGE_NAME)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onSliceUnpinned_interactedPackageIsInstalled_shouldKeepPackage() {
|
||||||
|
mockInteractedPackageAsInstalled();
|
||||||
|
|
||||||
|
mNotificationChannelWorker.onSliceUnpinned();
|
||||||
|
|
||||||
|
final Set<String> interactedPackages = mSharedPreferences.getStringSet(
|
||||||
|
PREF_KEY_INTERACTED_PACKAGES, new ArraySet<>());
|
||||||
|
assertThat(interactedPackages.contains(PACKAGE_NAME)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockInteractedPackageAsInstalled() {
|
||||||
|
final PackageInfo packageInfo = new PackageInfo();
|
||||||
|
packageInfo.packageName = PACKAGE_NAME;
|
||||||
|
mPackageManager.addPackage(packageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInteractedPackageToSharedPreference() {
|
||||||
|
final Set<String> interactedPackages = new ArraySet<>();
|
||||||
|
interactedPackages.add(PACKAGE_NAME);
|
||||||
|
|
||||||
|
mSharedPreferences.edit().putStringSet(PREF_KEY_INTERACTED_PACKAGES,
|
||||||
|
interactedPackages).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInteractedPackageFromSharedPreference() {
|
||||||
|
if (mSharedPreferences.contains(PREF_KEY_INTERACTED_PACKAGES)) {
|
||||||
|
mSharedPreferences.edit().remove(PREF_KEY_INTERACTED_PACKAGES).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user