Merge "Whitelist default apps for DND access." into nyc-mr1-dev
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 3f8bad1..de52f73 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -73,4 +73,11 @@
      * any locks in this method.
      */
     public abstract void onSystemLocaleChangedNoLock();
+
+    /**
+     * Called by PM before sending package broadcasts to other components.  PM doesn't hold the PM
+     * lock, but do not take any locks in here anyway, and don't do any heavy tasks, as doing so
+     * would slow down all the package broadcasts.
+     */
+    public abstract void onPackageBroadcast(Intent intent);
 }
diff --git a/docs/html/distribute/stories/apps.jd b/docs/html/distribute/stories/apps.jd
index 9c2e8e9..76e9f5a 100644
--- a/docs/html/distribute/stories/apps.jd
+++ b/docs/html/distribute/stories/apps.jd
@@ -29,5 +29,5 @@
       data-sortOrder="-timestamp"
       data-cardSizes="6x6"
       data-items-per-page="15"
-      data-initial-results="3"></div>
+      data-initial-results="6"></div>
 </div></section>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/aftenposten.jd b/docs/html/distribute/stories/apps/aftenposten.jd
new file mode 100644
index 0000000..f1f388e
--- /dev/null
+++ b/docs/html/distribute/stories/apps/aftenposten.jd
@@ -0,0 +1,80 @@
+page.title=Aftenposten Improves Retention by Allowing Readers to Customize Notifications
+page.metaDescription=Aftenposten upgraded their app and improved user retention.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/aftenposten.png
+page.timestamp=1468270114
+
+@jd:body
+
+<div class="figure" style="width:113px">
+  <img src="{@docRoot}images/distribute/stories/aftenposten-icon.png" height=
+  "106">
+</div>
+
+<h3>
+  Background
+</h3>
+
+<p>
+  Aftenposten is one of the largest newspapers in Norway. Their <a class=
+  "external-link" href=
+  "https://play.google.com/store/apps/details?id=no.cita&amp;e=-EnableAppDetailsPageRedesign">
+  news app</a> was released on Android in 2013.
+</p>
+
+<p>
+  Aftenposten found that sending too many notifications, with no user control
+  over the default <em>on</em> setting or differentiation between general and
+  breaking news, caused many people to uninstall their app. They changed the
+  user controls for notifications and used the native Android share button in
+  the app, <strong>which reduced user uninstalls</strong>.
+</p>
+
+<h3>
+  What they did
+</h3>
+
+<p>
+  Aftenposten created a new onboarding flow that explained what notifications
+  were available, allowing readers to manage their preferences and customize up
+  to three topics. They also changed their custom share icon for the native
+  Android app.
+</p>
+
+<h3>
+  Results
+</h3>
+
+<p>
+  The results showed that with the new notifications management onboarding
+  screen, <strong>uninstalls decreased by 9.2% over 60 days</strong>. And with
+  the option to customize notifications, 51% of readers decided to keep two out
+  of three topics turned on. This led to a <strong>28% decrease over 60 days in
+  the number of users muting notifications completely</strong>. It also
+  provided insight into users’ content preferences, with <em>Sport</em> being
+  the least-favored notification.
+</p>
+
+<p>
+  Aftenposten also increased share interactions by 17% just by replacing their
+  custom share icon with the native Android share icon.
+</p>
+
+<p>
+  Aftenposten commented that: <em>Many of our users who see the onboarding
+  screen interact with it by turning off at least one notification topic. This
+  means that users are accepting push from one or more topics, instead of
+  turning it off completely. Moreover, readers are sharing more articles since
+  we added the standard share Android icon.</em>
+</p>
+
+<h3>
+  Get started
+</h3>
+
+<p>
+  Find out more about best practices for <a href=
+  "{@docRoot}design/patterns/notifications.html">Notifications</a> and <a href=
+  "{@docRoot}training/building-content-sharing.html">Building Apps with Content
+  Sharing</a>.
+</p>
diff --git a/docs/html/distribute/stories/apps/el-mundo.jd b/docs/html/distribute/stories/apps/el-mundo.jd
new file mode 100644
index 0000000..2ee813d
--- /dev/null
+++ b/docs/html/distribute/stories/apps/el-mundo.jd
@@ -0,0 +1,73 @@
+page.title=El Mundo Improves User Ratings and Engagement with Material Design
+page.metaDescription=El Mundo uses Material Design principles to enhance their app's user experience.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/el-mundo.png
+page.timestamp=1468270112
+
+@jd:body
+
+<div class="figure" style="width:113px">
+  <img src="{@docRoot}images/distribute/stories/el-mundo-icon.png" height=
+  "113">
+</div>
+
+<h3>
+  Background
+</h3>
+
+<p>
+  <a class="external-link" href=
+  "https://play.google.com/store/apps/details?id=com.gi.elmundo.main">El
+  Mundo</a>, one of Spain’s largest newspapers, integrated material design
+  principles into their app, which helped increase their Google Play Store
+  rating and improve user engagement.
+</p>
+
+<h3>
+  What they did
+</h3>
+
+<p>
+  El Mundo decided to completely redesign their app to provide a higher quality
+  user experience, making it easier for their readers to engage with news
+  content. By implementing material design guidelines, they created a
+  consistent look and feel throughout their app.
+</p>
+
+<p>
+  After analyzing user comments, El Mundo discovered that readers considered
+  their app to be complicated and out-of-date. Therefore, they decided to
+  simplify the app’s functionality by removing features that were redundant.
+  They also removed sections of their app that were less relevant to their
+  readers, such as weather updates and movie trailers. Finally, they applied a
+  brand new internal development framework that they now use consistently
+  across all of their apps.
+</p>
+
+<h3>
+  Results
+</h3>
+
+<p>
+  Following the re-launch of their material design app, El Mundo saw a
+  <strong>45% increase in the weekly install rate</strong>. Readers now spend
+  more time in the app, with the average time spent in-app increasing from one
+  to three minutes.
+</p>
+
+<p>
+  Additionally, this redesign resulted in more readers providing positive
+  feedback around the new experience, increasing the app rating in the Google
+  Play store by 25.8%, from 3.1 to 3.9.
+</p>
+
+<h3>
+  Get started
+</h3>
+
+<p>
+  Learn how to integrate <a class="external-link" href=
+  "https://material.google.com">Material Design</a> guidelines and follow
+  <a class="external-link" href="https://design.google.com">design
+  principles</a> for your app.
+</p>
diff --git a/docs/html/distribute/stories/apps/segundamano.jd b/docs/html/distribute/stories/apps/segundamano.jd
new file mode 100644
index 0000000..4cbf817
--- /dev/null
+++ b/docs/html/distribute/stories/apps/segundamano.jd
@@ -0,0 +1,63 @@
+page.title=Segundamano Develops Android-First as Its Fastest Channel for Growth
+page.metaDescription=Segundamano developed Android app to increase potential for growth.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/segundamano.png
+page.timestamp=1468270110
+
+@jd:body
+
+<div class="figure" style="width:113px">
+  <img src="{@docRoot}images/distribute/stories/segundamano-icon.png" height=
+  "113">
+</div>
+
+<h3>
+  Background
+</h3>
+
+<p>
+  <a class="external-link" href=
+  "https://play.google.com/store/apps/details?id=mx.segundamano.android">Segundamano</a>
+  is a leading shopping application in Mexico for second-hand products. They
+  started by placing classified ads in newspapers, progressed to desktop, and
+  over the past year have seen significant growth in mobile, which now accounts
+  for 70% of their business. They have also seen <strong>270% year-over-year
+  growth on the Android platform alone</strong>.
+</p>
+
+<h3>
+  What they did
+</h3>
+
+<p>
+  Segundamano shifted focus to mobile with their Android app because of the
+  high potential for growth. From July 2015 to January 2016, they saw an
+  increase of 55% in the number of classified ads on Android, higher than any
+  other platform. To leverage this momentum, Segundamano implemented two new
+  features on Android: premium offers and push notifications. Segundamano also
+  decided to implement material design in order to improve the in-app
+  experience and streamline the sales process for users.
+</p>
+
+<h3>
+  Results
+</h3>
+
+<p>
+  Following Segundamano’s enhancements to the user experience, they've seen an
+  increase in their star rating, a 4.7% lift in monthly active users, and a 7%
+  increase in sales of premium listings. Additionally, year-to-date, their
+  <strong>installs are over seven times higher on Android than on other
+  platforms</strong>.
+</p>
+
+<h3>
+  Get started
+</h3>
+
+<p>
+  Learn more about simplifying your in-app experience with <a href=
+  "{@docRoot}guide/topics/ui/notifiers/notifications.html">Notifications</a>
+  and the <a href="{@docRoot}design/material/index.html">material design
+  guidelines</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/distribute/stories/apps/tapps.jd b/docs/html/distribute/stories/apps/tapps.jd
new file mode 100644
index 0000000..1292139
--- /dev/null
+++ b/docs/html/distribute/stories/apps/tapps.jd
@@ -0,0 +1,366 @@
+page.title=Tapps Games Increases Installs by More Than 20% with Store Listing Experiments
+page.metaDescription=Tapps Games increased their use of store listing experiments in the Developer Console, with impressive results.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/tapps.png
+page.timestamp=1468270108
+
+@jd:body
+
+<style type="text/css">
+  span.positive{
+    color:green;
+    font-size: 125%;
+    font-weight:bold;">
+  }
+  span.negative{
+    color:red;
+    font-size: 125%;
+    font-weight:bold;">
+  }
+</style>
+
+<div class="figure" style="width:215px">
+  <img src="{@docRoot}images/distribute/stories/tapps-logo.png" height="65">
+</div>
+
+<h3>
+  Background
+</h3>
+
+<p>
+  <a class="external-link" href=
+  "https://play.google.com/store/apps/dev?id=6615809648420562690">Tapps</a> is
+  a mobile game publisher in São Paulo, Brazil. With a mission of <em>creating
+  fun for everyone</em>, Tapps has a portfolio of over 200 titles on the Google
+  Play Store, with roughly 70% of their installs coming from Android. Store
+  listing experiments have provided invaluable metrics to help their growing
+  team understand what makes the most effective product listings.
+</p>
+
+<h3>
+  What they did
+</h3>
+
+<p>
+  Tapps has increased their use of store listing experiments in the Developer
+  Console. They recently expanded their marketing team to allocate greater time
+  and designated resources to the Developer Console tools. <strong>"We can’t
+  stress enough how much value the store listing experiments have brought us
+  over the past months. Right now, our marketing team has a substantial time
+  allocated to these tests every week,"</strong> said Felipe Watanabe, head of
+  marketing at Tapps. With icons and screenshots, Tapps tested variations in
+  color, character positioning, and the overall amount of graphic detail. In
+  the description tests, they found that shorter messages with clear calls to
+  action and appropriate language localizations were most successful.
+</p>
+
+<h3>
+  Results
+</h3>
+
+<p>
+  By frequently conducting store listing experiments, Tapps gained valuable
+  insights that they have applied across their greater portfolio of games.
+  Results showed that shortening messaging, using contrasting colors,
+  reordering screenshots, and simplifying graphics often led to variant results
+  representing an average increase in performance between 5% and 50%. After
+  making changes based on the test results, Tapps saw <strong>install rates
+  increase beyond 20-30%</strong>.
+</p>
+
+<h4>
+  Screen tests
+</h4>
+
+<p>
+  The following table compares the install rates for three apps based on
+  changes to each app's screenshot.
+</p>
+
+<p class="table-caption">
+  <strong>Table 1</strong>. Screen test results
+</p>
+
+<table>
+  <tr>
+    <th>
+      Original
+    </th>
+    <th>
+      Variant
+    </th>
+    <th>
+      Variant results
+    </th>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-orig-3.png"
+      width="240">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-var-3.png"
+      width="240">
+    </td>
+    <td>
+      <span class="positive">+25%</span>
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-orig-1.png"
+      width="240">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-var-1.png"
+      width="240">
+    </td>
+    <td>
+      <span class="positive">+17.1%</span>
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-orig-2.png"
+      width="240">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-screen-var-2.png"
+      width="240">
+    </td>
+    <td>
+      <span class="positive">+7.4%</span>
+    </td>
+  </tr>
+
+</table>
+
+<h4>
+  Icon tests
+</h4>
+
+<p>
+  The following tables compare install rates for three apps based on changes
+  to each app's icon.
+</p>
+
+<p class="table-caption">
+  <strong>Table 2</strong>. Icon 1 test results
+</p>
+
+<table>
+  <tr>
+    <th>
+      Original
+    </th>
+    <th>
+      Variant 1
+    </th>
+    <th>
+      Variant 2
+    </th>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-orig-1.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-1.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-1-2.png">
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      ---
+    </td>
+    <td>
+      <span class="negative">-29.6%</span>
+    </td>
+    <td>
+      <span class="positive">+20.8%</span>
+    </td>
+  </tr>
+</table>
+
+<p class="table-caption">
+  <strong>Table 3</strong>. Icon 2 test results
+</p>
+
+<table>
+  <tr>
+    <th>
+      Original
+    </th>
+    <th>
+      Variant 1
+    </th>
+    <th>
+      Variant 2
+    </th>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-orig-2.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-2.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-2-2.png">
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      ---
+    </td>
+    <td>
+      <span class="positive">+5.1%</span>
+    </td>
+    <td>
+      <span class="positive">+19.7%</span>
+    </td>
+  </tr>
+</table>
+
+<p class="table-caption">
+  <strong>Table 4</strong>. Icon 3 test results
+</p>
+
+<table>
+  <tr>
+    <th>
+      Original
+    </th>
+    <th>
+      Variant 1
+    </th>
+    <th>
+      Variant 2
+    </th>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-orig-3.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-3.png">
+    </td>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-icon-var-3-2.png">
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      ---
+    </td>
+    <td>
+      <span class="negative">-17.7%</span>
+    </td>
+    <td>
+      <span class="positive">+50.7%</span>
+    </td>
+  </tr>
+</table>
+
+<h4>
+  Description tests
+</h4>
+
+<p>
+  The following table compares install rates for three apps based on changes to
+  each app's description text.
+</p>
+
+<p class="table-caption">
+  <strong>Table 5</strong>. Description test results
+</p>
+
+<table>
+  <tr>
+    <th>
+      Game
+    </th>
+    <th>
+      Original
+    </th>
+    <th>
+      Variant
+    </th>
+    <th>
+      Variant results
+    </th>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-logic-pic.png">
+      <strong>Logic Pic</strong>
+    </td>
+    <td>
+      <em>"Use logic to solve fun puzzles and discover hidden pictures! Logic
+      Pic is free!"</em>
+    </td>
+    <td>
+      <strong><em>"Discover all the hidden pictures in this challenging classic
+      japanese puzzle!"</em></strong>
+    </td>
+    <td>
+      <span class="positive">+10.7%</span>
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-candy-hills.png"
+      width="96"> <strong>Candy Hills</strong>
+    </td>
+    <td>
+      <em>"What will your candy park look like? Build it now in Candy
+      Hills!"</em>
+    </td>
+    <td>
+      <strong><em>"Build your own sweet candy park in Candy
+      Hills!"</em></strong>
+    </td>
+    <td>
+      <span class="positive">+8.2%</span>
+    </td>
+  </tr>
+
+  <tr>
+    <td>
+      <img src="{@docRoot}images/distribute/stories/tapps-villains-corp.png"
+      width="96"> <strong>Villains Corp.</strong>
+    </td>
+    <td>
+      <em>"Be a real villain and CONQUER THE WORLD!"</em>
+    </td>
+    <td>
+      <strong><em>"Mwahahaha! Be a real villain and CONQUER THE
+      WORLD!"</em></strong>
+    </td>
+    <td>
+      <span class="positive">+6.8%</span>
+    </td>
+  </tr>
+</table>
+
+<h3>
+  Get started
+</h3>
+
+<p>
+  Find out more about <a href=
+  "{@docRoot}distribute/users/experiments.html">store listing experiments</a>.
+</p>
diff --git a/docs/html/distribute/stories/apps/upbeat-games.jd b/docs/html/distribute/stories/apps/upbeat-games.jd
new file mode 100644
index 0000000..02222d3
--- /dev/null
+++ b/docs/html/distribute/stories/apps/upbeat-games.jd
@@ -0,0 +1,69 @@
+page.title=Witch Puzzle Achieves 98% of International Installs on Android
+page.metaDescription=Witch Puzzle localized their app into 12 languages.
+page.tags="developerstory", "apps", "googleplay"
+page.image=images/cards/distribute/stories/witch-puzzle.png
+page.timestamp=1468270106
+
+@jd:body
+
+
+<div class="figure">
+  <img src="{@docRoot}images/distribute/stories/witch-puzzle-icon.png"
+  width="113">
+</div>
+
+<h3>
+  Background
+</h3>
+
+<p>
+  Located in São Paulo, Brazil, <a class="external-link" href=
+  "https://play.google.com/store/apps/dev?id=8995071809141037139">Upbeat
+  Games</a> is an indie game developer with a mission to build fun and easy
+  games that anyone can play. As a small team, the Upbeat crew reacted quickly
+  to their game’s growing installs in Asian countries, and is now seeing strong
+  international growth with their game <a class="external-link" href=
+  "https://play.google.com/store/apps/details?id=com.upbeatgames.witchpuzzle">Witch
+  Puzzle</a>.
+</p>
+
+<h3>
+  What they did
+</h3>
+
+<p>
+  After noticing that Witch Puzzle was gaining traction throughout Asia, Upbeat
+  localized their game into 12 languages, prioritizing countries with an
+  existing user base and high gross national income (GNI). This led to a direct
+  increase in installs.
+</p>
+
+<div class="figure">
+  <img src="{@docRoot}images/distribute/stories/japanese-witch-puzzle.png"
+  width="214">
+  <p class="img-caption">
+    Japanese version of Witch Puzzle
+  </p>
+</div>
+
+<h3>
+  Results
+</h3>
+
+<p>
+  “In the last three months, 98% of our international installs for Witch Puzzle
+  came from Android,” said Vinicius Sormani Heimbeck, Upbeat’s founder. Upbeat
+  applied these learnings across their portfolio of games. Now, <strong>75% of
+  their portfolio’s revenue is driven by Android</strong>.
+</p>
+
+<h3>
+  Get started
+</h3>
+
+<p>
+  Use the <a href=
+  "{@docRoot}distribute/tools/localization-checklist.html">Localization
+  Checklist</a> to learn more about tailoring your app for different markets to
+  drive installs and revenue, and to create a better overall user experience.
+</p>
diff --git a/docs/html/images/cards/distribute/stories/aftenposten.png b/docs/html/images/cards/distribute/stories/aftenposten.png
new file mode 100644
index 0000000..60cb851
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/aftenposten.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/el-mundo.png b/docs/html/images/cards/distribute/stories/el-mundo.png
new file mode 100644
index 0000000..23db783
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/el-mundo.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/segundamano.png b/docs/html/images/cards/distribute/stories/segundamano.png
new file mode 100644
index 0000000..60e873c
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/segundamano.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/tapps.png b/docs/html/images/cards/distribute/stories/tapps.png
new file mode 100644
index 0000000..e01e3ad
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/tapps.png
Binary files differ
diff --git a/docs/html/images/cards/distribute/stories/witch-puzzle.png b/docs/html/images/cards/distribute/stories/witch-puzzle.png
new file mode 100644
index 0000000..c336f1b
--- /dev/null
+++ b/docs/html/images/cards/distribute/stories/witch-puzzle.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/aftenposten-icon.png b/docs/html/images/distribute/stories/aftenposten-icon.png
new file mode 100644
index 0000000..60cb851
--- /dev/null
+++ b/docs/html/images/distribute/stories/aftenposten-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/el-mundo-icon.png b/docs/html/images/distribute/stories/el-mundo-icon.png
new file mode 100644
index 0000000..23db783
--- /dev/null
+++ b/docs/html/images/distribute/stories/el-mundo-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/japanese-witch-puzzle.png b/docs/html/images/distribute/stories/japanese-witch-puzzle.png
new file mode 100644
index 0000000..6a7ef13
--- /dev/null
+++ b/docs/html/images/distribute/stories/japanese-witch-puzzle.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/segundamano-icon.png b/docs/html/images/distribute/stories/segundamano-icon.png
new file mode 100644
index 0000000..60e873c
--- /dev/null
+++ b/docs/html/images/distribute/stories/segundamano-icon.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-candy-hills.png b/docs/html/images/distribute/stories/tapps-candy-hills.png
new file mode 100644
index 0000000..14dcb94
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-candy-hills.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-orig-1.png b/docs/html/images/distribute/stories/tapps-icon-orig-1.png
new file mode 100644
index 0000000..44af423
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-orig-1.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-orig-2.png b/docs/html/images/distribute/stories/tapps-icon-orig-2.png
new file mode 100644
index 0000000..1b36255
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-orig-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-orig-3.png b/docs/html/images/distribute/stories/tapps-icon-orig-3.png
new file mode 100644
index 0000000..2f393f8
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-orig-3.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-1-2.png b/docs/html/images/distribute/stories/tapps-icon-var-1-2.png
new file mode 100644
index 0000000..fecab6e
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-1-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-1.png b/docs/html/images/distribute/stories/tapps-icon-var-1.png
new file mode 100644
index 0000000..77bd02a
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-1.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-2-2.png b/docs/html/images/distribute/stories/tapps-icon-var-2-2.png
new file mode 100644
index 0000000..84166c4
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-2-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-2.png b/docs/html/images/distribute/stories/tapps-icon-var-2.png
new file mode 100644
index 0000000..939c2fd
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-3-2.png b/docs/html/images/distribute/stories/tapps-icon-var-3-2.png
new file mode 100644
index 0000000..4aa782d
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-3-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-icon-var-3.png b/docs/html/images/distribute/stories/tapps-icon-var-3.png
new file mode 100644
index 0000000..1e44fdf
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-icon-var-3.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-logic-pic.png b/docs/html/images/distribute/stories/tapps-logic-pic.png
new file mode 100644
index 0000000..5029f79
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-logic-pic.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-logo.png b/docs/html/images/distribute/stories/tapps-logo.png
new file mode 100644
index 0000000..e01e3ad
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-logo.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-orig-1.png b/docs/html/images/distribute/stories/tapps-screen-orig-1.png
new file mode 100644
index 0000000..d54e75c
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-orig-1.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-orig-2.png b/docs/html/images/distribute/stories/tapps-screen-orig-2.png
new file mode 100644
index 0000000..a2d18e3
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-orig-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-orig-3.png b/docs/html/images/distribute/stories/tapps-screen-orig-3.png
new file mode 100644
index 0000000..e01fe20
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-orig-3.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-var-1.png b/docs/html/images/distribute/stories/tapps-screen-var-1.png
new file mode 100644
index 0000000..b930350
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-var-1.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-var-2.png b/docs/html/images/distribute/stories/tapps-screen-var-2.png
new file mode 100644
index 0000000..9ccb8a6
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-var-2.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-screen-var-3.png b/docs/html/images/distribute/stories/tapps-screen-var-3.png
new file mode 100644
index 0000000..8eb58e1
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-screen-var-3.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/tapps-villains-corp.png b/docs/html/images/distribute/stories/tapps-villains-corp.png
new file mode 100644
index 0000000..6e037da
--- /dev/null
+++ b/docs/html/images/distribute/stories/tapps-villains-corp.png
Binary files differ
diff --git a/docs/html/images/distribute/stories/witch-puzzle-icon.png b/docs/html/images/distribute/stories/witch-puzzle-icon.png
new file mode 100644
index 0000000..c336f1b
--- /dev/null
+++ b/docs/html/images/distribute/stories/witch-puzzle-icon.png
Binary files differ
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index aa79294..1928f92 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -799,7 +799,8 @@
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
-        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value)) {
+        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
+                Binder.getCallingUid())) {
             return false;
         }
 
@@ -930,7 +931,8 @@
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
-        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value)) {
+        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
+                Binder.getCallingUid())) {
             return false;
         }
 
@@ -1153,7 +1155,7 @@
      * @return true if the change is prohibited, false if the change is allowed.
      */
     private boolean isGlobalOrSecureSettingRestrictedForUser(String setting, int userId,
-            String value) {
+            String value, int callingUid) {
         String restriction;
         switch (setting) {
             case Settings.Secure.LOCATION_MODE:
@@ -1191,6 +1193,15 @@
                 restriction = UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS;
                 break;
 
+            case Settings.Secure.ALWAYS_ON_VPN_APP:
+            case Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
+                // Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
+                if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+                    return false;
+                }
+                restriction = UserManager.DISALLOW_CONFIG_VPN;
+                break;
+
             default:
                 if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) {
                     if ("0".equals(value)) return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5980715..1ca6148 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -159,6 +159,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
@@ -11438,6 +11439,9 @@
                     } else {
                         resolvedUserIds = userIds;
                     }
+                    final ShortcutServiceInternal shortcutService =
+                            LocalServices.getService(ShortcutServiceInternal.class);
+
                     for (int id : resolvedUserIds) {
                         final Intent intent = new Intent(action,
                                 pkg != null ? Uri.fromParts("package", pkg, null) : null);
@@ -11462,6 +11466,10 @@
                                     + intent.toShortString(false, true, false, false)
                                     + " " + intent.getExtras(), here);
                         }
+                        // TODO b/29385425 Consider making lifecycle callbacks for this.
+                        if (shortcutService != null) {
+                            shortcutService.onPackageBroadcast(intent);
+                        }
                         am.broadcastIntent(null, intent, null, finishedReceiver,
                                 0, null, null, null, android.app.AppOpsManager.OP_NONE,
                                 null, finishedReceiver != null, false, id);
diff --git a/services/core/java/com/android/server/pm/ShortcutPendingTasks.java b/services/core/java/com/android/server/pm/ShortcutPendingTasks.java
new file mode 100644
index 0000000..a5ace56
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutPendingTasks.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 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.server.pm;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.logging.Handler;
+
+/**
+ * Used by {@link ShortcutService} to register tasks to be executed on Handler and also wait for
+ * all pending tasks.
+ *
+ * Tasks can be registered with {@link #addTask(Runnable)}.  Call {@link #waitOnAllTasks()} to wait
+ * on all tasks that have been registered.
+ *
+ * In order to avoid deadlocks, {@link #waitOnAllTasks} MUST NOT be called with any lock held, nor
+ * on the handler thread.  These conditions are checked by {@link #mWaitThreadChecker} and wtf'ed.
+ *
+ * During unit tests, we can't run tasks asynchronously, so we just run Runnables synchronously,
+ * which also means the "is lock held" check doesn't work properly during unit tests (e.g. normally
+ * when a Runnable is executed on a Handler, the thread doesn't hold any lock, but during the tests
+ * we just run a Runnable on the thread that registers it, so the thread may or may not hold locks.)
+ * So unfortunately we have to disable {@link #mWaitThreadChecker} during unit tests.
+ *
+ * Because of the complications like those, this class should be used only for specific purposes:
+ * - {@link #addTask(Runnable)} should only be used to register tasks on callbacks from lower level
+ * services like the package manager or the activity manager.
+ *
+ * - {@link #waitOnAllTasks} should only be called at the entry point of RPC calls (or the test only
+ * accessors}.
+ */
+public class ShortcutPendingTasks {
+    private static final String TAG = "ShortcutPendingTasks";
+
+    private static final boolean DEBUG = false || ShortcutService.DEBUG; // DO NOT SUBMIT WITH TRUE.
+
+    private final Consumer<Runnable> mRunner;
+
+    private final BooleanSupplier mWaitThreadChecker;
+
+    private final Consumer<Throwable> mExceptionHandler;
+
+    /** # of tasks in the queue, including the running one. */
+    private final AtomicInteger mRunningTaskCount = new AtomicInteger();
+
+    /** For dumpsys */
+    private final AtomicLong mLastTaskStartTime = new AtomicLong();
+
+    /**
+     * Constructor.  In order to allow injection during unit tests, it doesn't take a
+     * {@link Handler} directly, and instead takes {@code runner} which will post an argument
+     * to a handler.
+     */
+    public ShortcutPendingTasks(Consumer<Runnable> runner, BooleanSupplier waitThreadChecker,
+            Consumer<Throwable> exceptionHandler) {
+        mRunner = runner;
+        mWaitThreadChecker = waitThreadChecker;
+        mExceptionHandler = exceptionHandler;
+    }
+
+    private static void dlog(String message) {
+        if (DEBUG) {
+            Slog.d(TAG, message);
+        }
+    }
+
+    /**
+     * Block until all tasks that are already queued finish.  DO NOT call it while holding any lock
+     * or on the handler thread.
+     */
+    public boolean waitOnAllTasks() {
+        dlog("waitOnAllTasks: enter");
+        try {
+            // Make sure it's not holding the lock.
+            if (!mWaitThreadChecker.getAsBoolean()) {
+                return false;
+            }
+
+            // Optimize for the no-task case.
+            if (mRunningTaskCount.get() == 0) {
+                return true;
+            }
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            addTask(latch::countDown);
+
+            for (; ; ) {
+                try {
+                    if (latch.await(1, TimeUnit.SECONDS)) {
+                        return true;
+                    }
+                    dlog("waitOnAllTasks: Task(s) still running...");
+                } catch (InterruptedException ignore) {
+                }
+            }
+        } finally {
+            dlog("waitOnAllTasks: exit");
+        }
+    }
+
+    /**
+     * Add a new task.  This operation is lock-free.
+     */
+    public void addTask(Runnable task) {
+        mRunningTaskCount.incrementAndGet();
+        mLastTaskStartTime.set(System.currentTimeMillis());
+
+        dlog("Task registered");
+
+        mRunner.accept(() -> {
+            try {
+                dlog("Task started");
+
+                task.run();
+            } catch (Throwable th) {
+                mExceptionHandler.accept(th);
+            } finally {
+                dlog("Task finished");
+                mRunningTaskCount.decrementAndGet();
+            }
+        });
+    }
+
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.print(prefix);
+        pw.print("Pending tasks:  # running tasks: ");
+        pw.println(mRunningTaskCount.get());
+
+        pw.print(prefix);
+        pw.print("  Last task started time: ");
+        final long lastStarted = mLastTaskStartTime.get();
+        pw.print(" [");
+        pw.print(lastStarted);
+        pw.print("] ");
+        pw.println(ShortcutService.formatTime(lastStarted));
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5f8cbbf..a9018b3 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -49,6 +49,7 @@
 import android.graphics.Canvas;
 import android.graphics.RectF;
 import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -81,7 +82,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -122,9 +122,6 @@
 
 /**
  * TODO:
- * - Deal with the async nature of PACKAGE_ADD.  Basically when a publisher does anything after
- *   it's upgraded, the manager should make sure the upgrade process has been executed.
- *
  * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
  *   -> But TypedValue.applyDimension() doesn't differentiate x and y..?
  *
@@ -304,6 +301,8 @@
 
     private final AtomicBoolean mBootCompleted = new AtomicBoolean();
 
+    private final ShortcutPendingTasks mPendingTasks;
+
     private static final int PACKAGE_MATCH_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -377,16 +376,41 @@
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 LocalServices.getService(UsageStatsManagerInternal.class));
 
+        mPendingTasks = new ShortcutPendingTasks(
+                this::injectPostToHandler,
+                this::injectCheckPendingTaskWaitThread,
+                throwable -> wtf(throwable.getMessage(), throwable));
+
         if (onlyForPackageManagerApis) {
             return; // Don't do anything further.  For unit tests only.
         }
 
-        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
-
         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
                 | ActivityManager.UID_OBSERVER_GONE);
     }
 
+    /**
+     * Check whether {@link ShortcutPendingTasks#waitOnAllTasks()} can be called on the current
+     * thread.
+     *
+     * During unit tests, all tasks are executed synchronously which makes the lock held check would
+     * misfire, so we override this method to always return true.
+     */
+    @VisibleForTesting
+    boolean injectCheckPendingTaskWaitThread() {
+        // We shouldn't wait while holding mLock.  We should never do this so wtf().
+        if (Thread.holdsLock(mLock)) {
+            wtf("waitOnAllTasks() called while holding the lock");
+            return false;
+        }
+        // This shouldn't be called on the handler thread either.
+        if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+            wtf("waitOnAllTasks() called on handler thread");
+            return false;
+        }
+        return true;
+    }
+
     void logDurationStat(int statId, long start) {
         synchronized (mStatLock) {
             mCountStats[statId]++;
@@ -1492,6 +1516,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1541,6 +1567,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1619,6 +1647,8 @@
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
         final int size = newShortcuts.size();
 
@@ -1669,6 +1699,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1696,6 +1728,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1716,6 +1750,8 @@
         verifyCaller(packageName, userId);
         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
@@ -1738,6 +1774,8 @@
     public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
         }
@@ -1750,6 +1788,9 @@
     public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1761,6 +1802,9 @@
     public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1772,6 +1816,9 @@
     public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -1801,6 +1848,8 @@
     public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return mMaxUpdatesPerInterval
                     - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
@@ -1811,6 +1860,8 @@
     public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         synchronized (mLock) {
             return getNextResetTimeLocked();
         }
@@ -1829,6 +1880,8 @@
     public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
         verifyCaller(packageName, userId);
 
+        mPendingTasks.waitOnAllTasks();
+
         Preconditions.checkNotNull(shortcutId);
 
         if (DEBUG) {
@@ -1861,6 +1914,8 @@
     public void resetThrottling() {
         enforceSystemOrShell();
 
+        mPendingTasks.waitOnAllTasks();
+
         resetThrottlingInner(getCallingUserId());
     }
 
@@ -1893,6 +1948,9 @@
         if (DEBUG) {
             Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
         }
+
+        mPendingTasks.waitOnAllTasks();
+
         enforceResetThrottlingPermission();
         resetPackageThrottling(packageName, userId);
     }
@@ -2055,6 +2113,14 @@
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
                 @Nullable ComponentName componentName,
                 int queryFlags, int userId) {
+
+            // When this method is called from onShortcutChangedInner() in LauncherApps,
+            // we're on the handler thread.  Do not try to wait on tasks.  Not waiting for pending
+            // tasks on this specific case should be fine.
+            if (Thread.currentThread() != mHandler.getLooper().getThread()) {
+                mPendingTasks.waitOnAllTasks();
+            }
+
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
             final boolean cloneKeyFieldOnly =
                     ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
@@ -2133,6 +2199,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2170,6 +2238,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkNotNull(shortcutIds, "shortcutIds");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 final ShortcutLauncher launcher =
                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
@@ -2190,6 +2260,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2220,6 +2292,8 @@
             Preconditions.checkNotNull(packageName, "packageName");
             Preconditions.checkNotNull(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2244,6 +2318,8 @@
             Preconditions.checkNotNull(packageName, "packageName");
             Preconditions.checkNotNull(shortcutId, "shortcutId");
 
+            mPendingTasks.waitOnAllTasks();
+
             synchronized (mLock) {
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
@@ -2304,9 +2380,18 @@
                 if (DEBUG) {
                     Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
                 }
-                injectPostToHandler(() -> handleLocaleChanged());
+                mPendingTasks.addTask(() -> handleLocaleChanged());
             }
         }
+
+        @Override
+        public void onPackageBroadcast(Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "onPackageBroadcast");
+            }
+            mPendingTasks.addTask(() -> ShortcutService.this.onPackageBroadcast(
+                    new Intent(intent)));
+        }
     }
 
     void handleLocaleChanged() {
@@ -2323,58 +2408,49 @@
         }
     }
 
-    /**
-     * Package event callbacks.
-     */
-    @VisibleForTesting
-    final PackageMonitor mPackageMonitor = new PackageMonitor() {
-
-        private boolean isUserUnlocked() {
-            return mUserManager.isUserUnlocked(getChangingUserId());
+    private void onPackageBroadcast(Intent intent) {
+        final int userId  = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+        if (userId == UserHandle.USER_NULL) {
+            Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
+            return;
         }
 
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // clearCallingIdentity is not needed normally, but need to do it for the unit test.
-            final long token = injectClearCallingIdentity();
-            try {
-                super.onReceive(context, intent);
-            } finally {
-                injectRestoreCallingIdentity(token);
+        final String action = intent.getAction();
+
+        if (!mUserManager.isUserUnlocked(userId)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring package broadcast " + action + " for locked/stopped user "
+                        + userId);
             }
+            return;
         }
 
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageAdded(packageName, getChangingUserId());
+        final Uri intentUri = intent.getData();
+        final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() : null;
+        if (packageName == null) {
+            Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
+            return;
         }
 
-        @Override
-        public void onPackageUpdateFinished(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageUpdateFinished(packageName, getChangingUserId());
-        }
+        final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
 
-        @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageRemoved(packageName, getChangingUserId());
-        }
+        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+            if (replacing) {
+                handlePackageUpdateFinished(packageName, userId);
+            } else {
+                handlePackageAdded(packageName, userId);
+            }
+        } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+            if (!replacing) {
+                handlePackageRemoved(packageName, userId);
+            }
+        } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+            handlePackageChanged(packageName, userId);
 
-        @Override
-        public void onPackageDataCleared(String packageName, int uid) {
-            if (!isUserUnlocked()) return;
-            handlePackageDataCleared(packageName, getChangingUserId());
+        } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) {
+            handlePackageDataCleared(packageName, userId);
         }
-
-        @Override
-        public boolean onPackageChanged(String packageName, int uid, String[] components) {
-            if (!isUserUnlocked()) return false;
-            handlePackageChanged(packageName, getChangingUserId());
-            return false; // We don't need to receive onSomePackagesChanged(), so just false.
-        }
-    };
+    }
 
     /**
      * Called when a user is unlocked.
@@ -3021,6 +3097,9 @@
                 pw.println(Log.getStackTraceString(mLastWtfStacktrace));
             }
 
+            pw.println();
+            mPendingTasks.dump(pw, "  ");
+
             for (int i = 0; i < mUsers.size(); i++) {
                 pw.println();
                 mUsers.valueAt(i).dump(pw, "  ");
@@ -3069,6 +3148,8 @@
 
         enforceShell();
 
+        mPendingTasks.waitOnAllTasks();
+
         final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
 
         resultReceiver.send(status, null);
@@ -3095,10 +3176,6 @@
                     case "--user":
                         if (takeUser) {
                             mUserId = UserHandle.parseUserArg(getNextArgRequired());
-                            if (!mUserManager.isUserUnlocked(mUserId)) {
-                                throw new CommandException(
-                                        "User " + mUserId + " is not running or locked");
-                            }
                             break;
                         }
                         // fallthrough
@@ -3424,6 +3501,7 @@
 
     @VisibleForTesting
     ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
+        mPendingTasks.waitOnAllTasks();
         synchronized (mLock) {
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
@@ -3434,8 +3512,12 @@
 
     @VisibleForTesting
     ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+        mPendingTasks.waitOnAllTasks();
         synchronized (mLock) {
-            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+            final ShortcutUser user = mUsers.get(userId);
+            if (user == null) return null;
+
+            final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
             if (pkg == null) return null;
 
             return pkg.findShortcutById(shortcutId);
@@ -3470,4 +3552,12 @@
             forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
         }
     }
+
+    ShortcutPendingTasks getPendingTasksForTest() {
+        return mPendingTasks;
+    }
+
+    Object getLockForTest() {
+        return mLock;
+    }
 }
diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
index 2e32fe3..c764833 100644
--- a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
@@ -134,7 +134,7 @@
     }
 
     public void immersiveModeChangedLw(String pkg, boolean isImmersiveMode,
-            boolean userSetupComplete) {
+            boolean userSetupComplete, boolean navBarEmpty) {
         mHandler.removeMessages(H.SHOW);
         if (isImmersiveMode) {
             final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
@@ -144,6 +144,7 @@
                     && (DEBUG_SHOW_EVERY_TIME || !mConfirmed)
                     && userSetupComplete
                     && !mVrModeEnabled
+                    && !navBarEmpty
                     && !UserManager.isDeviceInDemoMode(mContext)) {
                 mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
             }
@@ -152,12 +153,13 @@
         }
     }
 
-    public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode) {
+    public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode,
+            boolean navBarEmpty) {
         if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
             // turning the screen back on within the panic threshold
             return mClingWindow == null;
         }
-        if (isScreenOn && inImmersiveMode) {
+        if (isScreenOn && inImmersiveMode && !navBarEmpty) {
             // turning the screen off, remember if we were in immersive mode
             mPanicTime = time;
         } else {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3f71ba4..fbc727d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1024,7 +1024,8 @@
         // Detect user pressing the power button in panic when an application has
         // taken over the whole screen.
         boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
-                SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags));
+                SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
+                isNavBarEmpty(mLastSystemUiFlags));
         if (panic) {
             mHandler.post(mHiddenNavPanic);
         }
@@ -7590,7 +7591,7 @@
         if (win != null && oldImmersiveMode != newImmersiveMode) {
             final String pkg = win.getOwningPackage();
             mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
-                    isUserSetupComplete());
+                    isUserSetupComplete(), isNavBarEmpty(win.getSystemUiVisibility()));
         }
 
         vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
@@ -7649,6 +7650,14 @@
                 && canHideNavigationBar();
     }
 
+    private static boolean isNavBarEmpty(int systemUiFlags) {
+        final int disableNavigationBar = (View.STATUS_BAR_DISABLE_HOME
+                | View.STATUS_BAR_DISABLE_BACK
+                | View.STATUS_BAR_DISABLE_RECENT);
+
+        return (systemUiFlags & disableNavigationBar) == disableNavigationBar;
+    }
+
     /**
      * @return whether the navigation or status bar can be made translucent
      *
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 1be57bc..01c19d0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -404,6 +404,11 @@
             // During tests, WTF is fatal.
             fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
         }
+
+        @Override
+        boolean injectCheckPendingTaskWaitThread() {
+            return true;
+        }
     }
 
     /** ShortcutManager with injection override methods. */
@@ -848,6 +853,8 @@
 
     protected void shutdownServices() {
         if (mService != null) {
+            mService.getPendingTasksForTest().waitOnAllTasks();
+
             // Flush all the unsaved data from the previous instance.
             mService.saveDirtyInfo();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0936e46..c7673d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -62,6 +62,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
 import android.app.ActivityManager;
@@ -1297,8 +1298,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_3);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mInternal.onPackageBroadcast(genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -1316,8 +1316,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mInternal.onPackageBroadcast(genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
@@ -2815,7 +2814,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-            mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
         }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
                 .areAllManifest()
@@ -2852,7 +2851,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_0);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         assertForLauncherCallback(mLauncherApps, () -> {
@@ -3472,7 +3471,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3489,7 +3488,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3851,7 +3850,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertWith(getCallerShortcuts())
@@ -3891,7 +3890,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
 
         uninstallPackage(USER_0, CALLING_PACKAGE_1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -3911,7 +3910,7 @@
         mRunningUsers.put(USER_10, true);
 
         uninstallPackage(USER_10, CALLING_PACKAGE_2);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4002,7 +4001,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_1, USER_0));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4021,7 +4020,7 @@
 
         mRunningUsers.put(USER_10, true);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_2, USER_10));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4048,7 +4047,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4069,7 +4068,7 @@
         });
 
         // Clear data
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageDataClear(CALLING_PACKAGE_1, USER_10));
 
         // Only manifest shortcuts will remain, and are no longer pinned.
@@ -4134,9 +4133,9 @@
         reset(c0);
         reset(c10);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10));
 
         waitOnMainThread();
@@ -4157,7 +4156,7 @@
         updatePackageVersion(CALLING_PACKAGE_1, 1);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
 
         waitOnMainThread();
@@ -4222,7 +4221,7 @@
         updatePackageVersion(CALLING_PACKAGE_2, 10);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0));
         mService.handleUnlockUser(USER_10);
 
@@ -4246,7 +4245,7 @@
         updatePackageVersion(CALLING_PACKAGE_3, 100);
 
         // Then send the broadcast, to only user-0.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0));
         mService.handleUnlockUser(USER_10);
 
@@ -4328,7 +4327,7 @@
 
         // Update the package.
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -4357,7 +4356,7 @@
         mRunningUsers.put(USER_10, true);
 
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4389,7 +4388,7 @@
         });
 
         // First, no changes.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4412,7 +4411,7 @@
 
         // Disable activity 1
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4432,7 +4431,7 @@
         // Re-enable activity 1.
         // Manifest shortcuts will be re-published, but dynamic ones are not.
         mEnabledActivityChecker = (activity, userId) -> true;
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4456,7 +4455,7 @@
         // Disable activity 2
         // Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4519,7 +4518,7 @@
         setCaller(LAUNCHER_1, USER_0);
         assertForLauncherCallback(mLauncherApps, () -> {
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
         }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
                 // Make sure the launcher gets callbacks.
@@ -5635,7 +5634,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5656,7 +5655,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5693,7 +5692,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5730,7 +5729,7 @@
         mRunningUsers.put(USER_10, false);
         mUnlockedUsers.put(USER_10, false);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertEmpty(mManager.getManifestShortcuts());
@@ -5740,7 +5739,7 @@
         // Try again, but the user is locked, so still ignored.
         mRunningUsers.put(USER_10, true);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertEmpty(mManager.getManifestShortcuts());
@@ -5751,7 +5750,7 @@
         mUnlockedUsers.put(USER_10, true);
 
         // Send PACKAGE_ADD broadcast to have Package 2 on user-10 publish manifest shortcuts.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
 
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
@@ -5792,7 +5791,7 @@
                 R.xml.shortcut_5_reverse);
 
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
@@ -5820,7 +5819,7 @@
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
                 R.xml.shortcut_0);
         updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
         // No manifest shortcuts, and pinned ones are disabled.
@@ -5851,7 +5850,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5866,7 +5865,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5881,7 +5880,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_3);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -5897,7 +5896,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_4);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5925,7 +5924,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -5963,7 +5962,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_error_4);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Make sure 3, 4 and 5 still exist but disabled.
@@ -6011,7 +6010,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6116,7 +6115,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6213,7 +6212,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6232,7 +6231,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1_disable);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Because shortcut 1 wasn't pinned, it'll just go away.
@@ -6253,7 +6252,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Only the valid one is published.
@@ -6276,7 +6275,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1_disable);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         // Because shortcut 1 was pinned, it'll still exist as pinned, but disabled.
@@ -6309,7 +6308,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2_duplicate);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6339,7 +6338,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6411,7 +6410,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6461,7 +6460,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -6472,7 +6471,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6554,7 +6553,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_5);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -6624,7 +6623,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(2, mManager.getManifestShortcuts().size());
 
@@ -6750,7 +6749,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_2);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
             assertEquals(2, mManager.getManifestShortcuts().size());
@@ -6899,7 +6898,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                     R.xml.shortcut_1);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(1, mManager.getManifestShortcuts().size());
 
@@ -6919,7 +6918,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_1_alt);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(3, mManager.getManifestShortcuts().size());
 
@@ -6939,7 +6938,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_5_alt); // manifest has 5, but max is 3, so a2 will have 3.
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(5, mManager.getManifestShortcuts().size());
 
@@ -6958,7 +6957,7 @@
                     new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
                     R.xml.shortcut_0);
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mInternal.onPackageBroadcast(
                     genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
             assertEquals(0, mManager.getManifestShortcuts().size());
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index eb4db7a..fcf7ea2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -66,7 +66,7 @@
     private void publishManifestShortcuts(ComponentName activity, int resId) {
         addManifestShortcutResource(activity, resId);
         updatePackageVersion(CALLING_PACKAGE, 1);
-        mService.mPackageMonitor.onReceive(getTestContext(),
+        mInternal.onPackageBroadcast(
                 genPackageAddIntent(CALLING_PACKAGE, USER_0));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java
new file mode 100644
index 0000000..bf1ed98
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutPendingTasksTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 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.server.pm;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Run with:
+ adb install \
+   -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.pm.ShortcutPendingTasksTest \
+   -w com.android.frameworks.servicestests
+ */
+@LargeTest
+public class ShortcutPendingTasksTest extends BaseShortcutManagerTest {
+    public void testAll() {
+        final AtomicReference<Throwable> thrown = new AtomicReference<>();
+
+        final AtomicBoolean threadCheckerResult = new AtomicBoolean(true);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+
+        final ShortcutPendingTasks tasks = new ShortcutPendingTasks(
+                handler::post,
+                threadCheckerResult::get,
+                thrown::set);
+
+        // No pending tasks, shouldn't block.
+        assertTrue(tasks.waitOnAllTasks());
+
+        final AtomicInteger counter = new AtomicInteger();
+
+        // Run one task.
+        tasks.addTask(() -> {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+        });
+
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+
+        assertEquals(1, counter.get());
+
+        // Run 3 tasks.
+
+        // We use this ID to make sure only one task can run at the same time.
+        final AtomicInteger currentTaskId = new AtomicInteger();
+
+        tasks.addTask(() -> {
+            currentTaskId.set(1);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(1, currentTaskId.get());
+        });
+        tasks.addTask(() -> {
+            currentTaskId.set(2);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(2, currentTaskId.get());
+        });
+        tasks.addTask(() -> {
+            currentTaskId.set(3);
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException ignore) {
+            }
+            counter.incrementAndGet();
+            assertEquals(3, currentTaskId.get());
+        });
+
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+        assertEquals(4, counter.get());
+
+        // No tasks running, shouldn't block.
+        assertTrue(tasks.waitOnAllTasks());
+        assertNull(thrown.get());
+        assertEquals(4, counter.get());
+
+        // Now the thread checker returns false, so waitOnAllTasks() returns false.
+        threadCheckerResult.set(false);
+        assertFalse(tasks.waitOnAllTasks());
+
+        threadCheckerResult.set(true);
+
+        // Make sure the exception handler is called.
+        tasks.addTask(() -> {
+            throw new RuntimeException("XXX");
+        });
+        assertTrue(tasks.waitOnAllTasks());
+        assertNotNull(thrown.get());
+        MoreAsserts.assertContainsRegex("XXX", thrown.get().getMessage());
+    }
+}