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&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());
+ }
+}