diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d31fb3070d9..212290d81a6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1200,7 +1200,8 @@ + android:label="@string/notification_history_title" + android:taskAffinity="com.android.settings.notification"> @@ -2530,6 +2531,7 @@ diff --git a/res/layout/admin_support_details_dialog.xml b/res/layout/admin_support_details_dialog.xml index 66eaf0de62b..2e126cc9d33 100644 --- a/res/layout/admin_support_details_dialog.xml +++ b/res/layout/admin_support_details_dialog.xml @@ -34,7 +34,7 @@ android:layout_height="wrap_content" android:paddingStart="@dimen/admin_details_dialog_padding" android:text="@string/disabled_by_policy_title" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title" /> + android:textAppearance="@style/TextAppearance.HeadLineFontFamily"/> Some notifications and other content can appear as bubbles on the screen. To open a bubble, tap it. To dismiss it, drag it down the screen. Bubbles + + All Bubble settings + + Bubble this conversation - New messages will appear on screen + Show floating icon on top of apps Allow %1$s to show some notifications as bubbles diff --git a/res/values/styles.xml b/res/values/styles.xml index 848eaf14bd9..1359bce2211 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -733,4 +733,14 @@ sans-serif start + + + + diff --git a/res/xml/conversation_notification_settings.xml b/res/xml/conversation_notification_settings.xml index be1c980a05c..c034eb5d6d8 100644 --- a/res/xml/conversation_notification_settings.xml +++ b/res/xml/conversation_notification_settings.xml @@ -32,13 +32,23 @@ settings:allowDividerAbove="true" settings:allowDividerBelow="true"/> - - + + + + + + + 1.5f * GIB_IN_BYTES) { - final String bytesText = formatText(bytes / (float) GIB_IN_BYTES); - bytesPicker.setText(bytesText); - bytesPicker.setSelection(0, bytesText.length()); + final boolean unitInGigaBytes = (bytes > 1.5f * GIB_IN_BYTES); + final String bytesText = formatText(bytes, + unitInGigaBytes ? GIB_IN_BYTES : MIB_IN_BYTES); + bytesPicker.setText(bytesText); + bytesPicker.setSelection(0, bytesText.length()); - type.setSelection(1); - } else { - final String bytesText = formatText(bytes / (float) MIB_IN_BYTES); - bytesPicker.setText(bytesText); - bytesPicker.setSelection(0, bytesText.length()); - - type.setSelection(0); - } + type.setSelection(unitInGigaBytes ? 1 : 0); } - private String formatText(float v) { - v = Math.round(v * 100) / 100f; - return String.valueOf(v); + private String formatText(double v, double unitInBytes) { + final NumberFormat formatter = NumberFormat.getNumberInstance(); + formatter.setMaximumFractionDigits(2); + return formatter.format((double) (v / unitInBytes)); } @Override @@ -315,15 +324,22 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); - EditText bytesField = (EditText) mView.findViewById(R.id.bytes); - Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner); + final EditText bytesField = (EditText) mView.findViewById(R.id.bytes); + final Spinner spinner = (Spinner) mView.findViewById(R.id.size_spinner); - String bytesString = bytesField.getText().toString(); - if (bytesString.isEmpty() || bytesString.equals(".")) { - bytesString = "0"; + final String bytesString = bytesField.getText().toString(); + + final NumberFormat formatter = NumberFormat.getNumberInstance(); + Number number = null; + try { + number = formatter.parse(bytesString); + } catch (ParseException ex) { + } + long bytes = 0L; + if (number != null) { + bytes = (long) (number.floatValue() + * (spinner.getSelectedItemPosition() == 0 ? MIB_IN_BYTES : GIB_IN_BYTES)); } - final long bytes = (long) (Float.valueOf(bytesString) - * (spinner.getSelectedItemPosition() == 0 ? MIB_IN_BYTES : GIB_IN_BYTES)); // to fix the overflow problem final long correctedBytes = Math.min(MAX_DATA_LIMIT_BYTES, bytes); diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java index b800c179859..c7682577063 100644 --- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java +++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java @@ -26,11 +26,14 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import androidx.annotation.VisibleForTesting; import com.android.settings.slices.SliceBackgroundWorker; +import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; @@ -214,6 +217,17 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker return mPackageName; } + boolean hasAdjustVolumeUserRestriction() { + if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) { + return true; + } + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + return um.hasBaseUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, + UserHandle.of(UserHandle.myUserId())); + + } + private class DevicesChangedBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/com/android/settings/media/MediaOutputGroupSlice.java b/src/com/android/settings/media/MediaOutputGroupSlice.java index d60ae225e9e..402eb6bb7cf 100644 --- a/src/com/android/settings/media/MediaOutputGroupSlice.java +++ b/src/com/android/settings/media/MediaOutputGroupSlice.java @@ -95,7 +95,8 @@ public class MediaOutputGroupSlice implements CustomSliceable { GROUP_DEVICES.hashCode() + ACTION_MEDIA_SESSION_OPERATION, ACTION_MEDIA_SESSION_OPERATION), IconCompat.createWithBitmap(emptyBitmap), ListBuilder.ICON_IMAGE, ""); - if (maxVolume > 0) { // Add InputRange row + if (maxVolume > 0 && !getWorker().hasAdjustVolumeUserRestriction()) { + // Add InputRange row listBuilder.addInputRange(new ListBuilder.InputRangeBuilder() .setTitleItem(titleIcon, ListBuilder.ICON_IMAGE) .addEndItem(endItemAction) @@ -119,6 +120,7 @@ public class MediaOutputGroupSlice implements CustomSliceable { } private void addRow(ListBuilder listBuilder, List mediaDevices, boolean selected) { + final boolean adjustVolumeUserRestriction = getWorker().hasAdjustVolumeUserRestriction(); for (MediaDevice device : mediaDevices) { final int maxVolume = device.getMaxVolume(); final IconCompat titleIcon = Utils.createIconWithDrawable(device.getIcon()); @@ -133,7 +135,8 @@ public class MediaOutputGroupSlice implements CustomSliceable { IconCompat.createWithResource(mContext, R.drawable.ic_check_box_anim), "", selected); - if (maxVolume > 0) { // Add InputRange row + if (maxVolume > 0 && !adjustVolumeUserRestriction) { + // Add InputRange row final ListBuilder.InputRangeBuilder builder = new ListBuilder.InputRangeBuilder() .setTitleItem(titleIcon, ListBuilder.ICON_IMAGE) .setTitle(title) diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java index 27960e123ea..df9538b5773 100644 --- a/src/com/android/settings/media/MediaOutputSlice.java +++ b/src/com/android/settings/media/MediaOutputSlice.java @@ -96,7 +96,11 @@ public class MediaOutputSlice implements CustomSliceable { if (worker.getSelectedMediaDevice().size() > 1) { // Insert group item to the first when it is available - listBuilder.addInputRange(getGroupRow()); + if (worker.getSessionVolumeMax() > 0 && !worker.hasAdjustVolumeUserRestriction()) { + listBuilder.addInputRange(getGroupSliderRow()); + } else { + listBuilder.addRow(getGroupRow()); + } // Add all other devices for (MediaDevice device : devices) { addRow(device, null /* connectedDevice */, listBuilder); @@ -150,7 +154,7 @@ public class MediaOutputSlice implements CustomSliceable { return builder; } - private ListBuilder.InputRangeBuilder getGroupRow() { + private ListBuilder.InputRangeBuilder getGroupSliderRow() { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_speaker_group_black_24dp); final CharSequence sessionName = getWorker().getSessionName(); @@ -172,6 +176,24 @@ public class MediaOutputSlice implements CustomSliceable { return builder; } + private ListBuilder.RowBuilder getGroupRow() { + final IconCompat icon = IconCompat.createWithResource(mContext, + R.drawable.ic_speaker_group_black_24dp); + final CharSequence sessionName = getWorker().getSessionName(); + final CharSequence title = TextUtils.isEmpty(sessionName) + ? mContext.getString(R.string.media_output_group) : sessionName; + final PendingIntent broadcastAction = + getBroadcastIntent(mContext, MEDIA_GROUP_DEVICE, MEDIA_GROUP_DEVICE.hashCode()); + final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon, + ListBuilder.ICON_IMAGE, title); + final ListBuilder.RowBuilder builder = new ListBuilder.RowBuilder() + .setTitleItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setPrimaryAction(primarySliceAction) + .addEndItem(getEndItemSliceAction()); + return builder; + } + private void addRow(MediaDevice device, MediaDevice connectedDevice, ListBuilder listBuilder) { if (connectedDevice != null && TextUtils.equals(device.getId(), connectedDevice.getId())) { final String title = device.getName(); @@ -182,7 +204,7 @@ public class MediaOutputSlice implements CustomSliceable { final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon, ListBuilder.ICON_IMAGE, title); - if (device.getMaxVolume() > 0) { + if (device.getMaxVolume() > 0 && !getWorker().hasAdjustVolumeUserRestriction()) { final ListBuilder.InputRangeBuilder builder = new ListBuilder.InputRangeBuilder() .setTitleItem(icon, ListBuilder.ICON_IMAGE) .setTitle(title) @@ -368,6 +390,5 @@ public class MediaOutputSlice implements CustomSliceable { return getWorker() != null && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext) && getWorker().getMediaDevices().size() > 0; - } } diff --git a/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java b/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java index e0247885908..ca944daff46 100644 --- a/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java +++ b/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java @@ -52,13 +52,18 @@ public class NrDisabledInDsdsFooterPreferenceController extends BasePreferenceCo final TelephonyManager teleManager = ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) .createForSubscriptionId(mSubId); + final SubscriptionManager subManager = ((SubscriptionManager) + mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)); + final int[] activeSubIdList = subManager.getActiveSubscriptionIdList(); + final int activeSubCount = activeSubIdList == null ? 0 : activeSubIdList.length; // Show the footer only when DSDS is enabled, and mobile data is enabled on this SIM, and // 5G is supported on this device. - if (!teleManager.isDataEnabled() || teleManager.getActiveModemCount() < 2 - || !is5GSupportedByRadio(teleManager)) { + if (teleManager.isDataEnabled() && activeSubCount >= 2 && is5GSupportedByRadio(teleManager) + && !teleManager.canConnectTo5GInDsdsMode()) { + return AVAILABLE; + } else { return CONDITIONALLY_UNAVAILABLE; } - return AVAILABLE; } private boolean is5GSupportedByRadio(TelephonyManager tm) { diff --git a/src/com/android/settings/notification/app/BubbleCategoryPreferenceController.java b/src/com/android/settings/notification/app/BubbleCategoryPreferenceController.java new file mode 100644 index 00000000000..f8983ff39c7 --- /dev/null +++ b/src/com/android/settings/notification/app/BubbleCategoryPreferenceController.java @@ -0,0 +1,68 @@ +/* + * 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.provider.Settings.Global.NOTIFICATION_BUBBLES; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +public class BubbleCategoryPreferenceController extends NotificationPreferenceController { + + private static final String KEY = "bubbles"; + @VisibleForTesting + static final int ON = 1; + + public BubbleCategoryPreferenceController(Context context) { + super(context, null); + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + return areBubblesEnabled(); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (mAppRow != null) { + 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); + } + } + + + private boolean areBubblesEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, ON) == ON; + } +} diff --git a/src/com/android/settings/notification/app/BubbleLinkPreferenceController.java b/src/com/android/settings/notification/app/BubbleLinkPreferenceController.java new file mode 100644 index 00000000000..ff270436df4 --- /dev/null +++ b/src/com/android/settings/notification/app/BubbleLinkPreferenceController.java @@ -0,0 +1,68 @@ +/* + * 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.provider.Settings.Global.NOTIFICATION_BUBBLES; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +public class BubbleLinkPreferenceController extends NotificationPreferenceController { + + private static final String KEY = "notification_bubbles"; + @VisibleForTesting + static final int ON = 1; + + public BubbleLinkPreferenceController(Context context) { + super(context, null); + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + return areBubblesEnabled(); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (mAppRow != null) { + 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); + } + } + + + private boolean areBubblesEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, ON) == ON; + } +} diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java index 210af2086e6..9ee4a2cf2e1 100644 --- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java +++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java @@ -94,6 +94,8 @@ public class ConversationNotificationSettings extends NotificationSettings { mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(), mBackend, false /* isAppPage */)); mControllers.add(new ConversationDemotePreferenceController(context, this, mBackend)); + mControllers.add(new BubbleCategoryPreferenceController(context)); + mControllers.add(new BubbleLinkPreferenceController(context)); return new ArrayList<>(mControllers); } } diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java index 144d102e651..a5993e4a773 100644 --- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java +++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.INotificationManager; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.RemoteException; @@ -191,6 +192,20 @@ public class NotificationHistoryActivity extends Activity { super.onDestroy(); } + @Override + public void onBackPressed() { + handleBackPressed(); + } + + private void handleBackPressed() { + if (getFragmentManager().getBackStackEntryCount() > 1) { + super.onBackPressed(); + } else { + startActivity(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS)); + finish(); + } + } + private void bindSwitch() { if (mSwitchBar != null) { mSwitchBar.setSwitchBarText(R.string.notification_history_toggle,