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:
Yanting Yang
2019-04-08 20:30:02 +08:00
parent db7f321526
commit 78e097f239
6 changed files with 290 additions and 3 deletions

View File

@@ -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;
}
} }

View File

@@ -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;
} }

View File

@@ -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();
}
}

View File

@@ -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();
}
}
} }

View File

@@ -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;

View File

@@ -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();
}
}
}