Bubble settings: feature, notification, app

* Removed setting from developer options
* Removed bubble settings from normal notifications /
  channels

* Feature available via notification setting
* Feature screen with educational gif

* App level is now a tri-state choice of all / selected /
  none
* App level bubble controls are accessible top-level in
  app notifications

Test: make -j40 RunSettingsRoboTests ROBOTEST_FILTER="Bubble"
Bug: 138116133
Change-Id: Id103e9d3717fdc9b86a916be40c43cda9c35ac34
This commit is contained in:
Mady Mellor
2020-03-05 18:31:39 -08:00
parent 23040a3b2b
commit 409c3dfe68
29 changed files with 1183 additions and 411 deletions

View File

@@ -1,73 +0,0 @@
/*
* 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.development;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
public class BubbleGlobalPreferenceController extends DeveloperOptionsPreferenceController
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
@VisibleForTesting
static final int ON = 1;
@VisibleForTesting
static final int OFF = 0;
public BubbleGlobalPreferenceController(Context context) {
super(context);
}
@Override
public String getPreferenceKey() {
return Settings.Global.NOTIFICATION_BUBBLES;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
writeSetting((boolean) newValue);
return true;
}
@Override
public void updateState(Preference preference) {
((SwitchPreference) mPreference).setChecked(isEnabled());
}
@Override
protected void onDeveloperOptionsSwitchDisabled() {
super.onDeveloperOptionsSwitchDisabled();
writeSetting(false /* isEnabled */);
updateState(mPreference);
}
private boolean isEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_BUBBLES, OFF) == ON;
}
private void writeSetting(boolean isEnabled) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.NOTIFICATION_BUBBLES, isEnabled ? ON : OFF);
}
}

View File

@@ -508,7 +508,6 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
controllers.add(new DesktopModePreferenceController(context));
controllers.add(new SizeCompatFreeformPreferenceController(context));
controllers.add(new ShortcutManagerThrottlingPreferenceController(context));
controllers.add(new BubbleGlobalPreferenceController(context));
controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));
controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2020 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.notification;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/**
* Feature level screen for bubbles, available through notification menu.
* Allows user to turn bubbles on or off for the device.
*/
public class BubbleNotificationPreferenceController extends TogglePreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener,
LifecycleObserver, OnResume, OnPause {
private static final String TAG = "BubbleNotifPrefContr";
@VisibleForTesting
static final int ON = 1;
@VisibleForTesting
static final int OFF = 0;
private SettingObserver mSettingObserver;
public BubbleNotificationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
mSettingObserver = new SettingObserver(preference);
}
}
@Override
public void onResume() {
if (mSettingObserver != null) {
mSettingObserver.register(mContext.getContentResolver(), true /* register */);
}
}
@Override
public void onPause() {
if (mSettingObserver != null) {
mSettingObserver.register(mContext.getContentResolver(), false /* register */);
}
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, ON) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Global.putInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, isChecked ? ON : OFF);
}
@Override
public boolean isSliceable() {
return false;
}
class SettingObserver extends ContentObserver {
private final Uri NOTIFICATION_BUBBLES_URI =
Settings.Global.getUriFor(NOTIFICATION_BUBBLES);
private final Preference mPreference;
SettingObserver(Preference preference) {
super(new Handler());
mPreference = preference;
}
public void register(ContentResolver cr, boolean register) {
if (register) {
cr.registerContentObserver(NOTIFICATION_BUBBLES_URI, false, this);
} else {
cr.unregisterContentObserver(this);
}
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (NOTIFICATION_BUBBLES_URI.equals(uri)) {
updateState(mPreference);
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 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.notification;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.core.OnActivityResultListener;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@SearchIndexable
public class BubbleNotificationSettings extends DashboardFragment implements
OnActivityResultListener {
private static final String TAG = "BubbleNotiSettings";
@Override
public int getMetricsCategory() {
return SettingsEnums.BUBBLE_SETTINGS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.bubble_notification_settings;
}
/**
* For Search.
*/
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.bubble_notification_settings);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 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.notification;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* Summary of the feature setting for bubbles, available through notification menu.
*/
public class BubbleSummaryNotificationPreferenceController extends BasePreferenceController {
@VisibleForTesting
static final int ON = 1;
public BubbleSummaryNotificationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public CharSequence getSummary() {
return mContext.getString(
areBubblesEnabled()
? R.string.notifications_bubble_setting_on_summary
: R.string.switch_off_text);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
private boolean areBubblesEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, ON) == ON;
}
}

View File

@@ -24,6 +24,7 @@ import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
import android.app.role.RoleManager;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
@@ -81,7 +82,7 @@ public class NotificationBackend {
row.icon = IconDrawableFactory.newInstance(context).getBadgedIcon(app);
row.banned = getNotificationsBanned(row.pkg, row.uid);
row.showBadge = canShowBadge(row.pkg, row.uid);
row.allowBubbles = canBubble(row.pkg, row.uid);
row.bubblePreference = getBubblePreference(row.pkg, row.uid);
row.userId = UserHandle.getUserId(row.uid);
row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid);
row.channelCount = getChannelCount(row.pkg, row.uid);
@@ -192,18 +193,18 @@ public class NotificationBackend {
}
}
public boolean canBubble(String pkg, int uid) {
public int getBubblePreference(String pkg, int uid) {
try {
return sINM.areBubblesAllowedForPackage(pkg, uid);
return sINM.getBubblePreferenceForPackage(pkg, uid);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return false;
return -1;
}
}
public boolean setAllowBubbles(String pkg, int uid, boolean allow) {
public boolean setAllowBubbles(String pkg, int uid, int preference) {
try {
sINM.setBubblesAllowed(pkg, uid, allow);
sINM.setBubblesAllowed(pkg, uid, preference);
return true;
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
@@ -563,7 +564,7 @@ public class NotificationBackend {
public boolean systemApp;
public boolean lockedImportance;
public boolean showBadge;
public boolean allowBubbles;
public int bubblePreference = NotificationManager.BUBBLE_PREFERENCE_NONE;
public int userId;
public int blockedChannelCount;
public int channelCount;

View File

@@ -30,6 +30,9 @@ import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/**
* App level settings for bubbles.
*/
@SearchIndexable
public class AppBubbleNotificationSettings extends NotificationSettings implements
GlobalBubblePermissionObserverMixin.Listener {

View File

@@ -22,6 +22,10 @@ import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -29,10 +33,6 @@ import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
/** These settings are per app, so should not be returned in global search results. */
public class AppNotificationSettings extends NotificationSettings {
private static final String TAG = "AppNotificationSettings";
@@ -41,8 +41,7 @@ public class AppNotificationSettings extends NotificationSettings {
private static String KEY_ADVANCED_CATEGORY = "app_advanced";
private static String KEY_BADGE = "badge";
private static String KEY_APP_LINK = "app_link";
private static String KEY_BUBBLE = "bubble_link_pref";
private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK, KEY_BUBBLE};
private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK};
@Override
public int getMetricsCategory() {
@@ -121,9 +120,9 @@ public class AppNotificationSettings extends NotificationSettings {
mControllers.add(new DescriptionPreferenceController(context));
mControllers.add(new NotificationsOffPreferenceController(context));
mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
mControllers.add(new ChannelListPreferenceController(context, mBackend));
mControllers.add(new AppConversationListPreferenceController(context, mBackend));
mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
return new ArrayList<>(mControllers);
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2020 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.notification.app;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.Utils;
import com.android.settingslib.R;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedPreferenceHelper;
/**
* A tri-state preference allowing a user to specify what gets to bubble.
*/
public class BubblePreference extends Preference implements View.OnClickListener {
RestrictedPreferenceHelper mHelper;
private int mSelectedPreference;
private Context mContext;
private Drawable mSelectedBackground;
private Drawable mUnselectedBackground;
private ButtonViewHolder mBubbleAllButton;
private ButtonViewHolder mBubbleSelectedButton;
private ButtonViewHolder mBubbleNoneButton;
public BubblePreference(Context context) {
this(context, null);
}
public BubblePreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubblePreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public BubblePreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mHelper = new RestrictedPreferenceHelper(context, this, attrs);
mHelper.useAdminDisabledSummary(true);
mContext = context;
mSelectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
mUnselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
setLayoutResource(R.layout.bubble_preference);
}
public void setSelectedPreference(int preference) {
mSelectedPreference = preference;
}
public int getSelectedPreference() {
return mSelectedPreference;
}
public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
if (mHelper.setDisabledByAdmin(admin)) {
notifyChanged();
}
}
@Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final boolean disabledByAdmin = mHelper.isDisabledByAdmin();
View summary = holder.findViewById(android.R.id.summary);
if (disabledByAdmin) {
mHelper.onBindViewHolder(holder);
summary.setVisibility(View.VISIBLE);
} else {
summary.setVisibility(View.GONE);
}
holder.itemView.setClickable(false);
View bubbleAll = holder.findViewById(R.id.bubble_all);
ImageView bubbleAllImage = (ImageView) holder.findViewById(R.id.bubble_all_icon);
TextView bubbleAllText = (TextView) holder.findViewById(R.id.bubble_all_label);
mBubbleAllButton = new ButtonViewHolder(bubbleAll, bubbleAllImage, bubbleAllText,
BUBBLE_PREFERENCE_ALL);
mBubbleAllButton.setSelected(mContext, mSelectedPreference == BUBBLE_PREFERENCE_ALL);
bubbleAll.setTag(BUBBLE_PREFERENCE_ALL);
bubbleAll.setOnClickListener(this);
bubbleAll.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
View bubbleSelected = holder.findViewById(R.id.bubble_selected);
ImageView bubbleSelectedImage = (ImageView) holder.findViewById(R.id.bubble_selected_icon);
TextView bubbleSelectedText = (TextView) holder.findViewById(R.id.bubble_selected_label);
mBubbleSelectedButton = new ButtonViewHolder(bubbleSelected, bubbleSelectedImage,
bubbleSelectedText, BUBBLE_PREFERENCE_SELECTED);
mBubbleSelectedButton.setSelected(mContext,
mSelectedPreference == BUBBLE_PREFERENCE_SELECTED);
bubbleSelected.setTag(BUBBLE_PREFERENCE_SELECTED);
bubbleSelected.setOnClickListener(this);
bubbleSelected.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
View bubbleNone = holder.findViewById(R.id.bubble_none);
ImageView bubbleNoneImage = (ImageView) holder.findViewById(R.id.bubble_none_icon);
TextView bubbleNoneText = (TextView) holder.findViewById(R.id.bubble_none_label);
mBubbleNoneButton = new ButtonViewHolder(bubbleNone, bubbleNoneImage, bubbleNoneText,
BUBBLE_PREFERENCE_NONE);
mBubbleNoneButton.setSelected(mContext, mSelectedPreference == BUBBLE_PREFERENCE_NONE);
bubbleNone.setTag(BUBBLE_PREFERENCE_NONE);
bubbleNone.setOnClickListener(this);
bubbleNone.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
}
@Override
public void onClick(View v) {
final int selected = (int) v.getTag();
callChangeListener(selected);
mBubbleAllButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_ALL);
mBubbleSelectedButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_SELECTED);
mBubbleNoneButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_NONE);
}
private class ButtonViewHolder {
private View mView;
private ImageView mImageView;
private TextView mTextView;
private int mId;
ButtonViewHolder(View v, ImageView iv, TextView tv, int identifier) {
mView = v;
mImageView = iv;
mTextView = tv;
mId = identifier;
}
void setSelected(Context context, boolean selected) {
mView.setBackground(selected ? mSelectedBackground : mUnselectedBackground);
mView.setSelected(selected);
ColorStateList stateList = selected
? Utils.getColorAccent(context)
: Utils.getColorAttr(context, android.R.attr.textColorPrimary);
mImageView.setImageTintList(stateList);
mTextView.setTextColor(stateList);
}
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.notification.app;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.annotation.Nullable;
@@ -26,11 +27,14 @@ import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
/**
* Preference controller for Bubbles. This is used as the app-specific page and conversation
* settings.
*/
public class BubblePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
@@ -74,40 +78,49 @@ public class BubblePreferenceController extends NotificationPreferenceController
return true;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
if (mIsAppPage && mAppRow != null) {
// We're on the app specific bubble page which displays a tri-state
int backEndPref = mAppRow.bubblePreference;
BubblePreference pref = (BubblePreference) preference;
pref.setDisabledByAdmin(mAdmin);
if (mChannel != null) {
pref.setChecked(mChannel.canBubble() && isGloballyEnabled());
pref.setEnabled(!pref.isDisabledByAdmin());
if (!isGloballyEnabled()) {
pref.setSelectedPreference(BUBBLE_PREFERENCE_NONE);
} else {
pref.setChecked(mAppRow.allowBubbles && isGloballyEnabled());
pref.setSummary(mContext.getString(
R.string.bubbles_app_toggle_summary, mAppRow.label));
pref.setSelectedPreference(backEndPref);
}
} else if (mChannel != null) {
// We're on the channel specific notification page which displays a toggle.
RestrictedSwitchPreference switchpref = (RestrictedSwitchPreference) preference;
switchpref.setDisabledByAdmin(mAdmin);
switchpref.setChecked(mChannel.canBubble() && isGloballyEnabled());
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean value = (Boolean) newValue && isGloballyEnabled();
if (mChannel != null) {
mChannel.setAllowBubbles(value);
// Channel page is toggle
mChannel.setAllowBubbles((boolean) newValue);
saveChannel();
return true;
} else if (mAppRow != null && mFragmentManager != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
// if the global setting is off, toggling app level permission requires extra
// confirmation
if (!isGloballyEnabled() && !pref.isChecked()) {
new BubbleWarningDialogFragment()
.setPkgInfo(mAppRow.pkg, mAppRow.uid)
.show(mFragmentManager, "dialog");
return false;
} else {
mAppRow.allowBubbles = value;
mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
} else if (mIsAppPage) {
// App page is bubble preference
BubblePreference pref = (BubblePreference) preference;
if (mAppRow != null && mFragmentManager != null) {
final int value = (int) newValue;
if (!isGloballyEnabled()
&& pref.getSelectedPreference() == BUBBLE_PREFERENCE_NONE) {
// if the global setting is off, toggling app level permission requires extra
// confirmation
new BubbleWarningDialogFragment()
.setPkgPrefInfo(mAppRow.pkg, mAppRow.uid, value)
.show(mFragmentManager, "dialog");
return false;
} else {
mAppRow.bubblePreference = value;
mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
}
}
}
return true;
@@ -118,21 +131,26 @@ public class BubblePreferenceController extends NotificationPreferenceController
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
}
// Used in app level prompt that confirms the user is ok with turning on bubbles
// globally. If they aren't, undo what
/**
* Used in app level prompt that confirms the user is ok with turning on bubbles
* globally. If they aren't, undo that.
*/
public static void revertBubblesApproval(Context mContext, String pkg, int uid) {
NotificationBackend backend = new NotificationBackend();
backend.setAllowBubbles(pkg, uid, false);
backend.setAllowBubbles(pkg, uid, BUBBLE_PREFERENCE_NONE);
// changing the global settings will cause the observer on the host page to reload
// correct preference state
Settings.Global.putInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
}
// Apply global bubbles approval
public static void applyBubblesApproval(Context mContext, String pkg, int uid) {
/**
* Apply global bubbles approval
*/
public static void applyBubblesApproval(Context mContext, String pkg, int uid, int pref) {
NotificationBackend backend = new NotificationBackend();
backend.setAllowBubbles(pkg, uid, true);
backend.setAllowBubbles(pkg, uid, pref);
// changing the global settings will cause the observer on the host page to reload
// correct preference state
Settings.Global.putInt(mContext.getContentResolver(),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* Copyright (C) 2020 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.
@@ -16,38 +16,34 @@
package com.android.settings.notification.app;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.content.Intent;
import android.content.res.Resources;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
public class BubbleSummaryPreferenceController extends NotificationPreferenceController {
import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
/**
* Summary of the app setting for bubbles, available through app notification settings.
*/
public class BubbleSummaryPreferenceController extends NotificationPreferenceController {
private static final String KEY = "bubble_pref_link";
private static final String KEY = "bubble_link_pref";
@VisibleForTesting
static final int SYSTEM_WIDE_ON = 1;
@VisibleForTesting
static final int SYSTEM_WIDE_OFF = 0;
static final int ON = 1;
public BubbleSummaryPreferenceController(Context context, NotificationBackend backend) {
super(context, backend);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
@@ -63,45 +59,47 @@ public class BubbleSummaryPreferenceController extends NotificationPreferenceCon
if (isDefaultChannel()) {
return true;
} else {
return mAppRow != null && mAppRow.allowBubbles;
return mAppRow != null;
}
}
return isGloballyEnabled();
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (mAppRow != null) {
Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(AppBubbleNotificationSettings.class.getName())
.setArguments(args)
.setSourceMetricsCategory(
SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
.toIntent());
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, mAppRow.pkg);
intent.putExtra(Settings.EXTRA_APP_UID, mAppRow.uid);
preference.setIntent(intent);
}
}
@Override
public CharSequence getSummary() {
boolean canBubble = false;
if (mAppRow != null) {
if (mChannel != null) {
canBubble |= mChannel.canBubble() && isGloballyEnabled();
} else {
canBubble |= mAppRow.allowBubbles && isGloballyEnabled();
}
if (mAppRow == null) {
return null;
}
int backEndPref = mAppRow.bubblePreference;
Resources res = mContext.getResources();
if (backEndPref == BUBBLE_PREFERENCE_NONE || !isGloballyEnabled()) {
return res.getString(R.string.bubble_app_setting_none);
} else if (backEndPref == BUBBLE_PREFERENCE_ALL) {
return res.getString(R.string.bubble_app_setting_all);
} else {
return res.getString(R.string.bubble_app_setting_selected);
}
return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text);
}
private boolean isGloballyEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
NOTIFICATION_BUBBLES, ON) == ON;
}
}

View File

@@ -27,6 +27,7 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
static final String KEY_PKG = "p";
static final String KEY_UID = "u";
static final String KEY_SELECTED_PREFERENCE = "pref";
@Override
@@ -34,10 +35,11 @@ public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
return SettingsEnums.DIALOG_APP_BUBBLE_SETTINGS;
}
public BubbleWarningDialogFragment setPkgInfo(String pkg, int uid) {
public BubbleWarningDialogFragment setPkgPrefInfo(String pkg, int uid, int preference) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putInt(KEY_UID, uid);
args.putInt(KEY_SELECTED_PREFERENCE, preference);
setArguments(args);
return this;
}
@@ -48,6 +50,7 @@ public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
final Bundle args = getArguments();
final String pkg = args.getString(KEY_PKG);
final int uid = args.getInt(KEY_UID);
final int pref = args.getInt(KEY_SELECTED_PREFERENCE);
final String title =
getResources().getString(R.string.bubbles_feature_disabled_dialog_title);
@@ -60,7 +63,7 @@ public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
.setPositiveButton(R.string.bubbles_feature_disabled_button_approve,
(dialog, id) ->
BubblePreferenceController.applyBubblesApproval(
getContext(), pkg, uid))
getContext(), pkg, uid, pref))
.setNegativeButton(R.string.bubbles_feature_disabled_button_cancel,
(dialog, id) ->
BubblePreferenceController.revertBubblesApproval(