Increased the collapsed size of messaging notifications

Messaging notifications now get an increased boost in size,
since those are usually important to the user.

Test: existing tests pass
Bug: 34469375
Change-Id: Idfc2d2403b04c4c2d17b821e3ccbbbd48d31654d
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 80f4985..1c9acae 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -48,6 +48,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
 import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -3854,10 +3855,24 @@
          *   3. Standard template view
          */
         public RemoteViews createContentView() {
+            return createContentView(false /* increasedheight */ );
+        }
+
+        /**
+         * Construct a RemoteViews for the smaller content view.
+         *
+         *   @param increasedHeight true if this layout be created with an increased height. Some
+         *   styles may support showing more then just that basic 1U size
+         *   and the system may decide to render important notifications
+         *   slightly bigger even when collapsed.
+         *
+         *   @hide
+         */
+        public RemoteViews createContentView(boolean increasedHeight) {
             if (mN.contentView != null && (mStyle == null || !mStyle.displayCustomViewInline())) {
                 return mN.contentView;
             } else if (mStyle != null) {
-                final RemoteViews styleView = mStyle.makeContentView();
+                final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
                 if (styleView != null) {
                     return styleView;
                 }
@@ -4487,6 +4502,19 @@
     }
 
     /**
+     * @return the style class of this notification
+     * @hide
+     */
+    public Class<? extends Notification.Style> getNotificationStyle() {
+        String templateClass = extras.getString(Notification.EXTRA_TEMPLATE);
+
+        if (!TextUtils.isEmpty(templateClass)) {
+            return Notification.getNotificationStyleClass(templateClass);
+        }
+        return null;
+    }
+
+    /**
      * @return true if this notification is colorized. This also factors in wheather the
      * notification is ongoing.
      *
@@ -4607,11 +4635,13 @@
         }
 
         /**
-         * Construct a Style-specific RemoteViews for the final 1U notification layout.
+         * Construct a Style-specific RemoteViews for the collapsed notification layout.
          * The default implementation has nothing additional to add.
+         *
+         * @param increasedHeight true if this layout be created with an increased height.
          * @hide
          */
-        public RemoteViews makeContentView() {
+        public RemoteViews makeContentView(boolean increasedHeight) {
             return null;
         }
 
@@ -4957,6 +4987,23 @@
         }
 
         /**
+         * @param increasedHeight true if this layout be created with an increased height.
+         *
+         * @hide
+         */
+        @Override
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            if (increasedHeight) {
+                ArrayList<Action> actions = mBuilder.mActions;
+                mBuilder.mActions = new ArrayList<>();
+                RemoteViews remoteViews = makeBigContentView();
+                mBuilder.mActions = actions;
+                return remoteViews;
+            }
+            return super.makeContentView(increasedHeight);
+        }
+
+        /**
          * @hide
          */
         public RemoteViews makeBigContentView() {
@@ -5220,17 +5267,25 @@
          * @hide
          */
         @Override
-        public RemoteViews makeContentView() {
-            Message m = findLatestIncomingMessage();
-            CharSequence title = mConversationTitle != null
-                    ? mConversationTitle
-                    : (m == null) ? null : m.mSender;
-            CharSequence text = (m == null)
-                    ? null
-                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            if (!increasedHeight) {
+                Message m = findLatestIncomingMessage();
+                CharSequence title = mConversationTitle != null
+                        ? mConversationTitle
+                        : (m == null) ? null : m.mSender;
+                CharSequence text = (m == null)
+                        ? null
+                        : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
 
-            return mBuilder.applyStandardTemplateWithActions(mBuilder.getBaseLayoutResource(),
-                    mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+                return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
+                        mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+            } else {
+                ArrayList<Action> actions = mBuilder.mActions;
+                mBuilder.mActions = new ArrayList<>();
+                RemoteViews remoteViews = makeBigContentView();
+                mBuilder.mActions = actions;
+                return remoteViews;
+            }
         }
 
         private Message findLatestIncomingMessage() {
@@ -5795,7 +5850,7 @@
          * @hide
          */
         @Override
-        public RemoteViews makeContentView() {
+        public RemoteViews makeContentView(boolean increasedHeight) {
             return makeMediaContentView();
         }
 
@@ -5971,7 +6026,7 @@
          * @hide
          */
         @Override
-        public RemoteViews makeContentView() {
+        public RemoteViews makeContentView(boolean increasedHeight) {
             return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView);
         }
 
@@ -6085,8 +6140,8 @@
          * @hide
          */
         @Override
-        public RemoteViews makeContentView() {
-            RemoteViews remoteViews = super.makeContentView();
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
             return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
                     mBuilder.mN.contentView);
         }
@@ -6108,7 +6163,7 @@
                 return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
                         customRemoteView);
             } else if (customRemoteView != mBuilder.mN.contentView){
-                remoteViews = super.makeContentView();
+                remoteViews = super.makeContentView(false /* increasedHeight */);
                 return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
                         customRemoteView);
             } else {
diff --git a/core/java/com/android/internal/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
new file mode 100644
index 0000000..518cf41
--- /dev/null
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 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.internal.util;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import java.util.Objects;
+
+/**
+ * A util to look up messaging related functions for notifications. This is used for both the
+ * ranking and the actual layout.
+ */
+public class NotificationMessagingUtil {
+
+    private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
+    private final Context mContext;
+    private ArrayMap<Integer, String> mDefaultSmsApp = new ArrayMap<>();
+
+    public NotificationMessagingUtil(Context context) {
+        mContext = context;
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
+        final int userId = sbn.getUserId();
+        if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+        if (mDefaultSmsApp.get(userId) == null) {
+            cacheDefaultSmsApp(userId);
+        }
+        return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
+    }
+
+    private void cacheDefaultSmsApp(int userId) {
+        mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+    }
+
+    private final ContentObserver mSmsContentObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int userId) {
+            if (Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING).equals(uri)) {
+                cacheDefaultSmsApp(userId);
+            }
+        }
+    };
+
+    public boolean isImportantMessaging(StatusBarNotification sbn, int importance) {
+        if (importance < NotificationManager.IMPORTANCE_LOW) {
+            return false;
+        }
+
+        Class<? extends Notification.Style> style = sbn.getNotification().getNotificationStyle();
+        if (Notification.MessagingStyle.class.equals(style)) {
+            return true;
+        }
+
+        if (Notification.CATEGORY_MESSAGE.equals(sbn.getNotification().category)
+                && isDefaultMessagingApp(sbn)) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ddcc4ba..cf18373 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -69,6 +69,9 @@
     <!-- Height of a small notification in the status bar-->
     <dimen name="notification_min_height">92dp</dimen>
 
+    <!-- Height of a small notification in the status bar if it is a large (like messaging)-->
+    <dimen name="notification_min_height_large">132dp</dimen>
+
     <!-- Height of a small notification in the status bar which was used before android N -->
     <dimen name="notification_min_height_legacy">64dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index d1245b1..f19d6d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -77,6 +77,7 @@
     private int mMaxHeadsUpHeightLegacy;
     private int mMaxHeadsUpHeight;
     private int mNotificationMinHeight;
+    private int mNotificationMinHeightLarge;
     private int mNotificationMaxHeight;
     private int mNotificationAmbientHeight;
     private int mIncreasedPaddingBetweenElements;
@@ -207,6 +208,7 @@
     private Runnable mOnDismissRunnable;
     private boolean mIsLowPriority;
     private boolean mIsColorized;
+    private boolean mUseIncreasedCollapsedHeight;
 
     @Override
     public boolean isGroupExpansionChanging() {
@@ -341,8 +343,14 @@
         boolean customView = layout.getContractedChild().getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
-        int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
-                mNotificationMinHeightLegacy : mNotificationMinHeight;
+        int minHeight;
+        if (customView && beforeN && !mIsSummaryWithChildren) {
+            minHeight = mNotificationMinHeightLegacy;
+        } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
+            minHeight = mNotificationMinHeightLarge;
+        } else {
+            minHeight = mNotificationMinHeight;
+        }
         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
@@ -979,6 +987,10 @@
         }
     }
 
+    public void setUseIncreasedCollapsedHeight(boolean use) {
+        mUseIncreasedCollapsedHeight = use;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -992,6 +1004,8 @@
     private void initDimens() {
         mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
         mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
+        mNotificationMinHeightLarge = getFontScaledHeight(
+                R.dimen.notification_min_height_large);
         mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
         mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
         mMaxHeadsUpHeightLegacy = getFontScaledHeight(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index bbfcf31..bb9b070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -122,13 +122,13 @@
         }
 
         public boolean cacheContentViews(Context ctx, Notification updatedNotification,
-                boolean isLowPriority) {
+                boolean isLowPriority, boolean useIncreasedCollapsedView) {
             boolean applyInPlace = false;
             if (updatedNotification != null) {
                 final Notification.Builder updatedNotificationBuilder
                         = Notification.Builder.recoverBuilder(ctx, updatedNotification);
                 final RemoteViews newContentView = createContentView(updatedNotificationBuilder,
-                        isLowPriority);
+                        isLowPriority, useIncreasedCollapsedView);
                 final RemoteViews newBigContentView = createBigContentView(
                         updatedNotificationBuilder, isLowPriority);
                 final RemoteViews newHeadsUpContentView =
@@ -158,7 +158,8 @@
                 final Notification.Builder builder
                         = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
 
-                cachedContentView = createContentView(builder, isLowPriority);
+                cachedContentView = createContentView(builder, isLowPriority,
+                        useIncreasedCollapsedView);
                 cachedBigContentView = createBigContentView(builder, isLowPriority);
                 cachedHeadsUpContentView = builder.createHeadsUpContentView();
                 cachedPublicContentView = builder.makePublicContentView();
@@ -184,11 +185,11 @@
         }
 
         private RemoteViews createContentView(Notification.Builder builder,
-                boolean isAmbient) {
-            if (isAmbient) {
+                boolean isLowPriority, boolean useLarge) {
+            if (isLowPriority) {
                 return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
             }
-            return builder.createContentView();
+            return builder.createContentView(useLarge);
         }
 
         // Returns true if the RemoteViews are the same.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 018d888..6ace987 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1,3 +1,5 @@
+
+
 /*
  * Copyright (C) 2010 The Android Open Source Project
  *
@@ -112,6 +114,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationMessagingUtil;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -701,6 +704,7 @@
         }
     };
 
+    private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
     private NetworkController mNetworkController;
@@ -763,6 +767,7 @@
                 Context.DEVICE_POLICY_SERVICE);
 
         mNotificationData = new NotificationData(this);
+        mMessagingUtil = new NotificationMessagingUtil(mContext);
 
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -6318,8 +6323,10 @@
 
         final StatusBarNotification sbn = entry.notification;
         boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+                mNotificationData.getImportance(sbn.getKey()));
         try {
-            entry.cacheContentViews(mContext, null, isLowPriority);
+            entry.cacheContentViews(mContext, null, isLowPriority, useIncreasedCollapsedHeight);
         } catch (RuntimeException e) {
             Log.e(TAG, "Unable to get notification remote views", e);
             return false;
@@ -6489,6 +6496,7 @@
             row.setUserExpanded(userExpanded);
         }
         row.setUserLocked(userLocked);
+        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         row.onNotificationUpdated(entry);
         return true;
     }
@@ -6978,10 +6986,13 @@
         Notification n = notification.getNotification();
         mNotificationData.updateRanking(ranking);
 
+        boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(notification,
+                mNotificationData.getImportance(notification.getKey()));
+        entry.row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
         boolean applyInPlace;
         try {
             applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
-                    mNotificationData.isAmbient(key));
+                    mNotificationData.isAmbient(key), useIncreasedCollapsedHeight);
         } catch (RuntimeException e) {
             Log.e(TAG, "Unable to get notification remote views", e);
             applyInPlace = false;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 6f49df4..279bf90 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -31,6 +31,8 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 
+import com.android.internal.util.NotificationMessagingUtil;
+
 import java.util.Comparator;
 import java.util.Objects;
 
@@ -40,18 +42,15 @@
 public class NotificationComparator
         implements Comparator<NotificationRecord> {
 
-    private final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
-
     private final Context mContext;
+    private final NotificationMessagingUtil mMessagingUtil;
     private String mDefaultPhoneApp;
-    private ArrayMap<Integer, String> mDefaultSmsApp = new ArrayMap<>();
 
     public NotificationComparator(Context context) {
         mContext = context;
         mContext.registerReceiver(mPhoneAppBroadcastReceiver,
                 new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
+        mMessagingUtil = new NotificationMessagingUtil(mContext);
     }
 
     @Override
@@ -73,9 +72,15 @@
             return -1 * Boolean.compare(leftImportantOngoing, rightImportantOngoing);
         }
 
+        boolean leftMessaging = isImportantMessaging(left);
+        boolean rightMessaging = isImportantMessaging(right);
+        if (leftMessaging != rightMessaging) {
+            return -1 * Boolean.compare(leftMessaging, rightMessaging);
+        }
+
         // Next: sufficiently import person to person communication
-        boolean leftPeople = isImportantMessaging(left);
-        boolean rightPeople = isImportantMessaging(right);
+        boolean leftPeople = isImportantPeople(left);
+        boolean rightPeople = isImportantPeople(right);
 
         if (leftPeople && rightPeople){
             // by contact proximity, close to far. if same proximity, check further fields.
@@ -128,50 +133,31 @@
         if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
             return false;
         }
-
         // TODO: add whitelist
 
         return isCall(record) || isMediaNotification(record);
     }
 
-    protected boolean isImportantMessaging(NotificationRecord record) {
+    protected boolean isImportantPeople(NotificationRecord record) {
         if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
             return false;
         }
-
-        Class<? extends Notification.Style> style = getNotificationStyle(record);
-        if (Notification.MessagingStyle.class.equals(style)) {
-            return true;
-        }
-
         if (record.getContactAffinity() > ValidateNotificationPeople.NONE) {
             return true;
         }
-
-        if (record.getNotification().category == Notification.CATEGORY_MESSAGE
-                && isDefaultMessagingApp(record)) {
-            return true;
-        }
-
         return false;
     }
 
+    protected boolean isImportantMessaging(NotificationRecord record) {
+        return mMessagingUtil.isImportantMessaging(record.sbn, record.getImportance());
+    }
+
     private boolean isOngoing(NotificationRecord record) {
         final int ongoingFlags =
                 Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_ONGOING_EVENT;
         return (record.getNotification().flags & ongoingFlags) != 0;
     }
 
-    private Class<? extends Notification.Style> getNotificationStyle(NotificationRecord record) {
-        String templateClass =
-                record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
-
-        if (!TextUtils.isEmpty(templateClass)) {
-            return Notification.getNotificationStyleClass(templateClass);
-        }
-        return null;
-    }
-
     private boolean isMediaNotification(NotificationRecord record) {
         return record.getNotification().extras.getParcelable(
                 Notification.EXTRA_MEDIA_SESSION) != null;
@@ -191,18 +177,6 @@
         return Objects.equals(pkg, mDefaultPhoneApp);
     }
 
-    @SuppressWarnings("deprecation")
-    private boolean isDefaultMessagingApp(NotificationRecord record) {
-        final int userId = record.getUserId();
-        if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
-        if (mDefaultSmsApp.get(userId) == null) {
-            mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
-        }
-        return Objects.equals(mDefaultSmsApp.get(userId), record.sbn.getPackageName());
-    }
-
     private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -210,17 +184,4 @@
                     intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
         }
     };
-
-    private final ContentObserver mSmsContentObserver = new ContentObserver(
-            new Handler(Looper.getMainLooper())) {
-        @Override
-        public void onChange(boolean selfChange, Uri uri, int userId) {
-            if (Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING).equals(uri)) {
-                mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
-
-            }
-        }
-    };
 }