Launch channel settings as half sheet

If opening app has requested only a subset of fields

Test: manual - launch from an app, filtered and unfiltered, for
normal channels and conversations
Bug: 177246841
Change-Id: Ifd70478101d1ea1340d2ecc55033fab55e65ca92

Change-Id: I5194b959c82b2cfa7990c84285aaf69464cff3a8
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 86d7992..8191042 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2803,8 +2803,13 @@
         </activity>
 
         <!-- Show channel-level notification settings (channel passed in as extras) -->
-        <activity android:name="Settings$ChannelNotificationSettingsActivity"
+        <activity android:name=".notification.app.ChannelPanelActivity"
                   android:label="@string/notification_channel_title"
+                  android:theme="@style/Theme.Panel"
+                  android:launchMode="singleInstance"
+                  android:excludeFromRecents="true"
+                  android:noHistory="true"
+                  android:configChanges="orientation|keyboardHidden|screenSize"
                   android:exported="true">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
diff --git a/res/layout/notification_channel_panel.xml b/res/layout/notification_channel_panel.xml
new file mode 100644
index 0000000..49cd95c
--- /dev/null
+++ b/res/layout/notification_channel_panel.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/main_content"
+    android:layout_height="@dimen/output_switcher_slice_max_height"
+    android:background="@drawable/settings_panel_background"
+    android:orientation="vertical"
+    android:layout_width="match_parent">
+
+    <FrameLayout
+        android:id="@android:id/list_container"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:layout_width="match_parent"/>
+
+    <View
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/footer_divider"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/horizontal_divider_height"
+        android:background="?android:attr/dividerHorizontal"/>
+
+    <LinearLayout
+        android:id="@+id/footer"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp">
+
+        <Button
+            android:id="@+id/see_more"
+            style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:layout_marginStart="12dp"
+            android:text="@string/see_more"/>
+
+        <Space
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="match_parent" />
+
+        <Button
+            android:id="@+id/done"
+            style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:layout_marginEnd="12dp"
+            android:text="@string/done"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml
index 707cae9..95f7944 100644
--- a/res/xml/channel_notification_settings.xml
+++ b/res/xml/channel_notification_settings.xml
@@ -65,56 +65,56 @@
         android:summary="@string/promote_conversation_summary"
         settings:allowDividerAbove="true"/>
 
-        <!-- Default ringtone -->
-        <com.android.settings.notification.app.NotificationSoundPreference
-            android:key="ringtone"
-            android:title="@string/notification_channel_sound_title"
-            android:dialogTitle="@string/notification_channel_sound_title"
-            android:order="11"
-            android:showSilent="true"
-            android:showDefault="true"/>
+    <!-- Default ringtone -->
+    <com.android.settings.notification.app.NotificationSoundPreference
+        android:key="ringtone"
+        android:title="@string/notification_channel_sound_title"
+        android:dialogTitle="@string/notification_channel_sound_title"
+        android:order="11"
+        android:showSilent="true"
+        android:showDefault="true"/>
 
-        <!-- Vibration -->
-        <com.android.settingslib.RestrictedSwitchPreference
-            android:key="vibrate"
-            android:order="12"
-            android:title="@string/notification_vibrate_title"
-            settings:useAdditionalSummary="true" />
+    <!-- Vibration -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="vibrate"
+        android:order="12"
+        android:title="@string/notification_vibrate_title"
+        settings:useAdditionalSummary="true" />
 
-        <!-- Visibility Override -->
-        <com.android.settings.RestrictedListPreference
-            android:key="visibility_override"
-            android:order="13"
-            android:title="@string/app_notification_visibility_override_title"/>
+    <!-- Visibility Override -->
+    <com.android.settings.RestrictedListPreference
+        android:key="visibility_override"
+        android:order="13"
+        android:title="@string/app_notification_visibility_override_title"/>
 
-        <!-- Lights -->
-        <com.android.settingslib.RestrictedSwitchPreference
-            android:key="lights"
-            android:order="14"
-            android:title="@string/notification_show_lights_title"
-            settings:useAdditionalSummary="true"/>
+    <!-- Lights -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="lights"
+        android:order="14"
+        android:title="@string/notification_show_lights_title"
+        settings:useAdditionalSummary="true"/>
 
-        <!-- Show badge -->
-        <com.android.settingslib.RestrictedSwitchPreference
-            android:key="badge"
-            android:order="15"
-            android:title="@string/notification_channel_badge_title"
-            settings:useAdditionalSummary="true"
-            settings:restrictedSwitchSummary="@string/enabled_by_admin"/>
+    <!-- Show badge -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="badge"
+        android:order="15"
+        android:title="@string/notification_channel_badge_title"
+        settings:useAdditionalSummary="true"
+        settings:restrictedSwitchSummary="@string/enabled_by_admin"/>
 
-        <!-- Bypass DND -->
-        <com.android.settingslib.RestrictedSwitchPreference
-            android:key="bypass_dnd"
-            android:order="17"
-            android:title="@string/app_notification_override_dnd_title"
-            android:summary="@string/app_notification_override_dnd_summary"
-            settings:useAdditionalSummary="true"/>
+    <!-- Bypass DND -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="bypass_dnd"
+        android:order="17"
+        android:title="@string/app_notification_override_dnd_title"
+        android:summary="@string/app_notification_override_dnd_summary"
+        settings:useAdditionalSummary="true"/>
 
-        <Preference
-            android:key="app_link"
-            android:order="18"
-            android:title="@string/app_settings_link"
-            settings:allowDividerAbove="true"/>
+    <Preference
+        android:key="app_link"
+        android:order="18"
+        android:title="@string/app_settings_link"
+        settings:allowDividerAbove="true"/>
 
     <com.android.settings.notification.app.NotificationFooterPreference
         android:key="desc"
diff --git a/res/xml/conversation_notification_settings.xml b/res/xml/conversation_notification_settings.xml
index 65fdee5..ea555c2 100644
--- a/res/xml/conversation_notification_settings.xml
+++ b/res/xml/conversation_notification_settings.xml
@@ -62,10 +62,6 @@
         android:key="block_desc"
         settings:allowDividerAbove="false"/>
 
-    <PreferenceCategory
-        android:key="channel_advanced"
-        android:order="50"
-        settings:initialExpandedChildrenCount="0">
 
         <!-- peeking -->
         <com.android.settingslib.RestrictedSwitchPreference
@@ -107,6 +103,5 @@
             android:icon="@drawable/ic_volume_ringer_vibrate"
             android:title="@string/notification_vibrate_title"
             settings:useAdditionalSummary="true" />
-    </PreferenceCategory>
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/notification/app/ChannelNotificationSettings.java b/src/com/android/settings/notification/app/ChannelNotificationSettings.java
index bdea30d..296863c 100644
--- a/src/com/android/settings/notification/app/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/app/ChannelNotificationSettings.java
@@ -68,12 +68,16 @@
 
         if (mChannel != null && !TextUtils.isEmpty(mChannel.getConversationId())
             && !mChannel.isDemoted()) {
-            startActivity(new SubSettingLauncher(mContext)
+            Intent intent = new SubSettingLauncher(mContext)
                     .setDestination(ConversationNotificationSettings.class.getName())
                     .setArguments(getArguments())
                     .setExtras(getIntent() != null ? getIntent().getExtras(): null)
                     .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_TOPIC_NOTIFICATION)
-                    .toIntent());
+                    .toIntent();
+            if (mPreferenceFilter != null) {
+                intent.setClass(mContext, ChannelPanelActivity.class);
+            }
+            startActivity(intent);
             finish();
             return;
         }
@@ -84,6 +88,7 @@
             controller.displayPreference(getPreferenceScreen());
         }
         updatePreferenceStates();
+        animatePanel();
     }
 
     @Override
diff --git a/src/com/android/settings/notification/app/ChannelPanelActivity.java b/src/com/android/settings/notification/app/ChannelPanelActivity.java
new file mode 100644
index 0000000..3ba118e
--- /dev/null
+++ b/src/com/android/settings/notification/app/ChannelPanelActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 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 android.app.settings.SettingsEnums;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.HideNonSystemOverlayMixin;
+import com.android.settings.core.SubSettingLauncher;
+
+/**
+ * Dialog Activity to host channel settings
+ */
+public class ChannelPanelActivity extends FragmentActivity {
+
+    private static final String TAG = "ChannelPanelActivity";
+
+    final Bundle mBundle = new Bundle();
+    NotificationSettings mPanelFragment;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (!getIntent().hasExtra(Settings.EXTRA_CHANNEL_FILTER_LIST)) {
+            launchFullSettings();
+        }
+
+        getApplicationContext().getTheme().rebase();
+        createOrUpdatePanel();
+        getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        setIntent(intent);
+        createOrUpdatePanel();
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+    }
+
+    private void launchFullSettings() {
+        Bundle extras = getIntent().getExtras();
+        extras.remove(Settings.EXTRA_CHANNEL_FILTER_LIST);
+        startActivity(new SubSettingLauncher(this)
+                .setDestination(ChannelNotificationSettings.class.getName())
+                .setExtras(extras)
+                .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_TOPIC_NOTIFICATION)
+                .toIntent());
+        finish();
+    }
+
+    private void createOrUpdatePanel() {
+        final Intent callingIntent = getIntent();
+        if (callingIntent == null) {
+            Log.e(TAG, "Null intent, closing Panel Activity");
+            finish();
+            return;
+        }
+
+        final FragmentManager fragmentManager = getSupportFragmentManager();
+        setContentView(R.layout.notification_channel_panel);
+
+        // Move the window to the bottom of screen, and make it take up the entire screen width.
+        final Window window = getWindow();
+        window.setGravity(Gravity.BOTTOM);
+        window.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.WRAP_CONTENT);
+
+        findViewById(R.id.done).setOnClickListener(v -> finish());
+        findViewById(R.id.see_more).setOnClickListener(v -> launchFullSettings());
+
+        mPanelFragment = callingIntent.hasExtra(Settings.EXTRA_CONVERSATION_ID)
+                ? new ConversationNotificationSettings()
+                : new ChannelNotificationSettings();
+        mPanelFragment.setArguments(new Bundle(mBundle));
+        fragmentManager.beginTransaction().replace(
+                android.R.id.list_container, mPanelFragment).commit();
+    }
+}
diff --git a/src/com/android/settings/notification/app/ConversationHeaderPreferenceController.java b/src/com/android/settings/notification/app/ConversationHeaderPreferenceController.java
index 313b387..73659b8 100644
--- a/src/com/android/settings/notification/app/ConversationHeaderPreferenceController.java
+++ b/src/com/android/settings/notification/app/ConversationHeaderPreferenceController.java
@@ -93,6 +93,7 @@
                     .done(activity, mContext);
 
             pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
+            pref.findViewById(R.id.entity_header).setBackground(null);
         }
     }
 
diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
index 819ba83..d712c84 100644
--- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java
+++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
@@ -49,10 +49,11 @@
 
         for (NotificationPreferenceController controller : mControllers) {
             controller.onResume(mAppRow, mChannel, mChannelGroup, mConversationDrawable,
-                    mConversationInfo, mSuspendedAppsAdmin, null);
+                    mConversationInfo, mSuspendedAppsAdmin, mPreferenceFilter);
             controller.displayPreference(getPreferenceScreen());
         }
         updatePreferenceStates();
+        animatePanel();
     }
 
     @Override
diff --git a/src/com/android/settings/notification/app/HeaderPreferenceController.java b/src/com/android/settings/notification/app/HeaderPreferenceController.java
index 6482943..4e9c039 100644
--- a/src/com/android/settings/notification/app/HeaderPreferenceController.java
+++ b/src/com/android/settings/notification/app/HeaderPreferenceController.java
@@ -92,6 +92,7 @@
                     .setRecyclerView(mFragment.getListView(), mFragment.getSettingsLifecycle())
                     .done(activity, mContext);
             pref.findViewById(R.id.entity_header).setVisibility(View.VISIBLE);
+            pref.findViewById(R.id.entity_header).setBackground(null);
         }
     }
 
diff --git a/src/com/android/settings/notification/app/NotificationSettings.java b/src/com/android/settings/notification/app/NotificationSettings.java
index 44c735a..99f067d 100644
--- a/src/com/android/settings/notification/app/NotificationSettings.java
+++ b/src/com/android/settings/notification/app/NotificationSettings.java
@@ -18,6 +18,9 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -39,8 +42,13 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.DecelerateInterpolator;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
@@ -85,6 +93,20 @@
     protected Intent mIntent;
     protected Bundle mArgs;
 
+    private ViewGroup mLayoutView;
+    private static final int DURATION_ANIMATE_PANEL_EXPAND_MS = 250;
+
+    private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
+            new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    animateIn();
+                    if (mLayoutView != null) {
+                        mLayoutView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                    }
+                }
+            };
+
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
@@ -187,6 +209,50 @@
         collectConfigActivities();
     }
 
+    protected void animatePanel() {
+        if (mPreferenceFilter != null) {
+            mLayoutView = getActivity().findViewById(R.id.main_content);
+            mLayoutView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
+        }
+    }
+
+    /**
+     * Animate a Panel onto the screen.
+     * <p>
+     * Takes the entire panel and animates in from behind the navigation bar.
+     * <p>
+     * Relies on the Panel being having a fixed height to begin the animation.
+     */
+    private void animateIn() {
+        final AnimatorSet animatorSet = buildAnimatorSet(mLayoutView,
+                mLayoutView.getHeight() /* startY */, 0.0f /* endY */,
+                0.0f /* startAlpha */, 1.0f /* endAlpha */,
+                DURATION_ANIMATE_PANEL_EXPAND_MS);
+        final ValueAnimator animator = new ValueAnimator();
+        animator.setFloatValues(0.0f, 1.0f);
+        animatorSet.play(animator);
+        animatorSet.start();
+    }
+
+    /**
+     * Build an {@link AnimatorSet} to animate the Panel, {@param parentView} in or out of the
+     * screen, based on the positional parameters {@param startY}, {@param endY}, the parameters
+     * for alpha changes {@param startAlpha}, {@param endAlpha}, and the {@param duration} in
+     * milliseconds.
+     */
+    @NonNull
+    private static AnimatorSet buildAnimatorSet(@NonNull View targetView,
+            float startY, float endY,
+            float startAlpha, float endAlpha, int duration) {
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.setDuration(duration);
+        animatorSet.setInterpolator(new DecelerateInterpolator());
+        animatorSet.playTogether(
+                ObjectAnimator.ofFloat(targetView, View.TRANSLATION_Y, startY, endY),
+                ObjectAnimator.ofFloat(targetView, View.ALPHA, startAlpha, endAlpha));
+        return animatorSet;
+    }
+
     private void loadPreferencesFilter() {
         Intent intent = getActivity().getIntent();
         mPreferenceFilter = intent != null