Merge changes from topic "motd" into stable-2.16

* changes:
  Document MessageOfTheDay extension
  Add UI element to display messages of the day
  Add MessageOfTheDay-entries to ServerInfo
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 974e370..64cae2d 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2954,6 +2954,40 @@
 are met, but marked as `OK`. If the requirements were not displayed, reviewers
 would need to use their precious time to manually check that they were met.
 
+[[message-of-the-day]]
+== Posting Messages (Of The Day) to the UI
+Gerrit provides an extension point that enables plugins to implement a method to
+collect messages that will then be shown below the main header in the Gerrit UI.
+
+[source, java]
+----
+import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
+
+@Singleton
+class MessageOfTheDayImpl extends MessageOfTheDay {
+
+  private final String id;
+  private final String msg;
+
+  public MessageOfTheDayImpl() {
+    id = "hello";
+    msg = "I just wanted to say <b>hello</b>.";
+  }
+
+  @Override
+  public String getHtmlMessage() {
+    return msg;
+  }
+
+  @Override
+  public String getMessageId() {
+    return id;
+  }
+}
+----
+
+Note, that the message will be added as HTML and parsed into the DOM. Thus,
+plugins using this extension should ensure that the message content is safe.
 
 == SEE ALSO
 
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 7be8c32..905c3ef 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1905,6 +1905,22 @@
 The number of open files.
 |============================
 
+[[message-of-the-day-info]]
+=== MessageOfTheDayInfo
+The `MessageOfTheDayInfo` entity contains information about a message
+that was registered with the
+link:dev-plugins.html#message-of-the-day[MessageOfTheDay]-extension by plugins.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name     ||Description
+|`id`           ||ID of the message.
+|`redisplay`    ||
+Date and Time, when the message should be displayed again after it was dismissed
+by the user.
+|`html`         ||Message in HTML-format.
+|===========================
+
 [[plugin-config-info]]
 === PluginConfigInfo
 The `PluginConfigInfo` entity contains information about Gerrit
@@ -1958,6 +1974,11 @@
 Information about the configuration from the
 link:config-gerrit.html#gerrit[gerrit] section as link:#gerrit-info[
 GerritInfo] entity.
+|`messages`                ||
+List of messages registered with the
+link:dev-plugins.html#message-of-the-day[MessageOfTheDay]-extension
+containing link:#message-of-the-day-info[
+MessageOfTheDayInfo] entities.
 |`note_db_enabled`         |not set if `false`|
 Whether the NoteDb storage backend is fully enabled.
 |`plugin`                  ||
diff --git a/java/com/google/gerrit/extensions/common/MessageOfTheDayInfo.java b/java/com/google/gerrit/extensions/common/MessageOfTheDayInfo.java
new file mode 100644
index 0000000..f752f86
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/MessageOfTheDayInfo.java
@@ -0,0 +1,27 @@
+// 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.google.gerrit.extensions.common;
+
+import java.util.Date;
+
+/** REST API representation of a "message of the day". */
+public class MessageOfTheDayInfo {
+  /** The ID of the message. */
+  public String id;
+  /** The date and time the message will be displayed again after being dismissed by the user. */
+  public Date redisplay;
+  /** The message in HTML-format. */
+  public String html;
+}
diff --git a/java/com/google/gerrit/extensions/common/ServerInfo.java b/java/com/google/gerrit/extensions/common/ServerInfo.java
index 8904f0a..27cf529 100644
--- a/java/com/google/gerrit/extensions/common/ServerInfo.java
+++ b/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+import java.util.List;
 import java.util.Map;
 
 public class ServerInfo {
@@ -22,6 +23,7 @@
   public ChangeConfigInfo change;
   public DownloadInfo download;
   public GerritInfo gerrit;
+  public List<MessageOfTheDayInfo> messages;
   public Boolean noteDbEnabled;
   public PluginConfigInfo plugin;
   public SshdInfo sshd;
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index eca26c3..04f4f8a 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.extensions.common.DownloadInfo;
 import com.google.gerrit.extensions.common.DownloadSchemeInfo;
 import com.google.gerrit.extensions.common.GerritInfo;
+import com.google.gerrit.extensions.common.MessageOfTheDayInfo;
 import com.google.gerrit.extensions.common.PluginConfigInfo;
 import com.google.gerrit.extensions.common.ReceiveInfo;
 import com.google.gerrit.extensions.common.ServerInfo;
@@ -36,7 +37,9 @@
 import com.google.gerrit.extensions.config.CloneCommand;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.server.EnableSignedPush;
 import com.google.gerrit.server.account.AccountVisibilityProvider;
@@ -69,6 +72,7 @@
 import java.util.Collection;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
@@ -100,6 +104,7 @@
   private final GerritOptions gerritOptions;
   private final ChangeIndexCollection indexes;
   private final SitePaths sitePaths;
+  private final DynamicSet<MessageOfTheDay> messages;
 
   @Inject
   public GetServerInfo(
@@ -123,7 +128,8 @@
       AgreementJson agreementJson,
       GerritOptions gerritOptions,
       ChangeIndexCollection indexes,
-      SitePaths sitePaths) {
+      SitePaths sitePaths,
+      DynamicSet<MessageOfTheDay> motd) {
     this.config = config;
     this.accountVisibilityProvider = accountVisibilityProvider;
     this.authConfig = authConfig;
@@ -145,6 +151,7 @@
     this.gerritOptions = gerritOptions;
     this.indexes = indexes;
     this.sitePaths = sitePaths;
+    this.messages = motd;
   }
 
   @Override
@@ -155,6 +162,7 @@
     info.change = getChangeInfo();
     info.download = getDownloadInfo();
     info.gerrit = getGerritInfo();
+    info.messages = getMessages();
     info.noteDbEnabled = toBoolean(isNoteDbEnabled());
     info.plugin = getPluginInfo();
     info.defaultTheme = getDefaultTheme();
@@ -325,6 +333,20 @@
     return CharMatcher.is('/').trimTrailingFrom(docUrl) + '/';
   }
 
+  private List<MessageOfTheDayInfo> getMessages() {
+    return this.messages.stream()
+        .filter(motd -> !Strings.isNullOrEmpty(motd.getHtmlMessage()))
+        .map(
+            motd -> {
+              MessageOfTheDayInfo m = new MessageOfTheDayInfo();
+              m.id = motd.getMessageId();
+              m.redisplay = motd.getRedisplay();
+              m.html = motd.getHtmlMessage();
+              return m;
+            })
+        .collect(toList());
+  }
+
   private boolean isNoteDbEnabled() {
     return migration.readChanges();
   }
diff --git a/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.html b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.html
new file mode 100644
index 0000000..b2b382c
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.html
@@ -0,0 +1,41 @@
+<!--
+@license
+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.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+
+<dom-module id="gr-message-header">
+  <template>
+    <style include="shared-styles">
+      #container {
+        background-color: lightyellow;
+        display: flex;
+        height: fit-content;
+        justify-content: space-between;
+        padding: 1em;
+      }
+    </style>
+    <div id="container" hidden$="[[_hidden]]">
+      <div id="message"></div>
+      <gr-button id="dismissMessageBtn"
+        link
+        on-tap="_handleDismissMessage">Dismiss</gr-button>
+    </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="../../../scripts/util.js"></script>
+  <script src="gr-message-header.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.js b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.js
new file mode 100644
index 0000000..fac4a6a
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header.js
@@ -0,0 +1,52 @@
+/**
+ * @license
+ * 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.
+ */
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-message-header',
+
+    properties: {
+      message: {
+        type: Object,
+        reflectToAttribute: true,
+      },
+      _hidden: {
+        type: Boolean,
+        value: true,
+      },
+    },
+
+    attached() {
+      if (!this.message || !this.message.html) {
+        return;
+      }
+      this._isHidden();
+      this.$.message.innerHTML = this.message.html;
+    },
+
+    _handleDismissMessage() {
+      document.cookie =
+        `msg-${this.message.id}=1; expires=${this.message.redisplay}`;
+      this._hidden = true;
+    },
+
+    _isHidden() {
+      this._hidden = window.util.getCookie(`msg-${this.message.id}`) === '1';
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header_test.html b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header_test.html
new file mode 100644
index 0000000..c275e55
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-message-header/gr-message-header_test.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<!--
+@license
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-message-header</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-message-header.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-message-header></gr-message-header>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-message-header tests', () => {
+    let element;
+
+    setup(() => {
+      element = fixture('basic');
+    });
+
+    test('show message', () => {
+      element.message = {html: 'This is a test message.'};
+      element.attached();
+      assert.equal(element.$.message.innerHTML, element.message.html);
+    });
+
+    test('hide message on dismiss', () => {
+      element.message = {html: 'This is a test message.', id: 'test'};
+      element.attached();
+      MockInteractions.tap(element.$.dismissMessageBtn);
+      assert.isTrue(element.$.container.hidden);
+      assert.isTrue(document.cookie.includes('msg-test=1'));
+
+      element.attached();
+      assert.isTrue(element.$.container.hidden);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 3a9719d..4fff9e62 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -55,6 +55,7 @@
 <link rel="import" href="./core/gr-error-manager/gr-error-manager.html">
 <link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html">
 <link rel="import" href="./core/gr-main-header/gr-main-header.html">
+<link rel="import" href="./core/gr-message-header/gr-message-header.html">
 <link rel="import" href="./core/gr-navigation/gr-navigation.html">
 <link rel="import" href="./core/gr-reporting/gr-reporting.html">
 <link rel="import" href="./core/gr-router/gr-router.html">
@@ -158,6 +159,12 @@
           class$="[[_computeShadowClass(_isShadowDom)]]">
       </gr-main-header>
     </gr-fixed-panel>
+    <template
+        is="dom-repeat"
+        items="[[_getMessages(_serverConfig)]]"
+        as="message">
+      <gr-message-header message="{{message}}"></gr-message-header>
+    </template>
     <main>
       <template is="dom-if" if="[[_showChangeListView]]" restamp="true">
         <gr-change-list-view
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index f48951b..ea7e86dc 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -339,6 +339,10 @@
           config.gerrit.web_uis && config.gerrit.web_uis.includes('GWT');
     },
 
+    _getMessages(config) {
+      return config.messages ? config.messages : [];
+    },
+
     _handlePageError(e) {
       const props = [
         '_showChangeListView',