Sandbox changes for use in UXR prototype.

- "All Set" screen doesn't show after Home/Overview/Back tutorials.
 - X button in top left is hidden (we'll want to move this to the
   feedback view).
 - Feedback pops in and out from the top of the screen.
 - Hand animation is replaced by video feedback at start and after
   incorrect gesture.
 - Back tutorial goes left then right, in order to match video.
 - Updated strings and marked translatable (UX-reviewed already).
 - Added Chinese translations.
 - Many other things.

Test: Manual
Change-Id: I126a3ea0dad645014fab9cdee2ed19e06a8a56e9
This commit is contained in:
Andy Wickham
2020-12-28 15:00:52 -08:00
parent 9a8c77afe9
commit 855072cda5
26 changed files with 750 additions and 299 deletions

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="394dp"
android:height="172dp"
android:viewportWidth="394"
android:viewportHeight="172">
<path
android:pathData="M20,0L374,0A20,20 0,0 1,394 20L394,152A20,20 0,0 1,374 172L20,172A20,20 0,0 1,0 152L0,20A20,20 0,0 1,20 0z"
android:fillColor="?android:attr/colorBackgroundFloating"
android:fillAlpha="0.9"/>
</vector>

View File

@@ -11,7 +11,237 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/gesture_tutorial_fake_task_view_color" />
</shape>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="892dp"
android:viewportWidth="412"
android:viewportHeight="892">
<path
android:pathData="M0,101h412v791h-412z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M28,222L282,222A4,4 0,0 1,286 226L286,254A4,4 0,0 1,282 258L28,258A4,4 0,0 1,24 254L24,226A4,4 0,0 1,28 222z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,266L344,266A4,4 0,0 1,348 270L348,298A4,4 0,0 1,344 302L28,302A4,4 0,0 1,24 298L24,270A4,4 0,0 1,28 266z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,310L257,310A4,4 0,0 1,261 314L261,342A4,4 0,0 1,257 346L28,346A4,4 0,0 1,24 342L24,314A4,4 0,0 1,28 310z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,354L67,354A4,4 0,0 1,71 358L71,362A4,4 0,0 1,67 366L28,366A4,4 0,0 1,24 362L24,358A4,4 0,0 1,28 354z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M86,354L125,354A4,4 0,0 1,129 358L129,362A4,4 0,0 1,125 366L86,366A4,4 0,0 1,82 362L82,358A4,4 0,0 1,86 354z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,390L384,390A4,4 0,0 1,388 394L388,626A4,4 0,0 1,384 630L28,630A4,4 0,0 1,24 626L24,394A4,4 0,0 1,28 390z"
android:fillColor="#5F6368"/>
<path
android:pathData="M101.322,469.362L71.101,577.73H131.543L101.322,469.362Z"
android:strokeWidth="2"
android:fillColor="#5F6368"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M101.322,522.516V543.644V611.743"
android:fillColor="#5F6368"/>
<path
android:pathData="M101.322,522.516V543.644V611.743"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M109.577,532.822L101.322,548.135"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M109.577,558.221L101.322,574.196"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M94.54,524.945L101.322,534.442"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M90.929,549.239L101.322,561.166"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M62.772,412.307L30.855,563.669H94.688L62.772,412.307Z"
android:strokeWidth="2"
android:fillColor="#5F6368"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M62.772,486.516V516.037V611.154"
android:fillColor="#5F6368"/>
<path
android:pathData="M62.772,486.516V516.037V611.154"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M74.344,500.944L62.772,522.368"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M74.344,536.43L62.772,558.736"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M53.263,489.976L62.772,503.154"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M48.251,523.914L62.772,540.552"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9ADCB2"/>
<path
android:pathData="M37,612H377"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#9AA0A6"/>
<path
android:pathData="M81.24,441.187L84.066,443.127L87.333,441.98L86.273,445.244L88.393,447.978H84.949L83.006,450.8L81.947,447.537L78.679,446.655L81.417,444.626L81.24,441.187Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M172.561,469L175.387,471.029L178.654,469.794L177.594,473.057L179.714,475.791H176.27L174.327,478.702L173.267,475.438L170,474.468L172.737,472.44L172.561,469Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M278.073,481.229L280.81,483.257L284.078,482.022L283.018,485.286L285.137,488.02H281.693L279.839,490.93L278.691,487.667L275.424,486.697L278.161,484.668L278.073,481.229Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M325.561,459L328.387,460.94L331.654,459.794L330.594,463.057L332.714,465.791H329.27L327.327,468.613L326.267,465.35L323,464.468L325.737,462.44L325.561,459Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M209.649,517L212.475,519.029L215.654,517.794L214.682,521.057L216.802,523.791H213.358L211.415,526.702L210.356,523.438L207,522.468L209.737,520.44L209.649,517Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M128.836,482.816L129.455,485.11L131.662,485.815L129.808,487.314L129.896,489.784L128.042,488.285L125.922,489.078L126.629,486.785L125.304,484.757L127.512,484.845L128.836,482.816Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M37.532,450L38.239,452.381L40.358,453.087L38.504,454.498L38.592,456.968L36.737,455.556L34.618,456.35L35.325,453.969L34,452.029H36.208L37.532,450Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M284.077,516.947L284.784,519.329L286.903,520.034L285.048,521.445L285.137,523.915L283.282,522.415L281.163,523.297L281.869,520.916L280.545,518.976H282.753L284.077,516.947Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M232.771,444.893L233.478,447.274L235.597,447.979L233.743,449.391L233.831,451.86L231.977,450.449L229.857,451.243L230.564,448.861L229.239,446.921H231.447L232.771,444.893Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M152.384,521L152.914,522.676L154.415,523.117L153.091,524.175L153.179,525.851L151.854,524.881L150.442,525.41L150.883,523.734L150,522.411H151.501L152.384,521Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M136.473,515L136.914,516.676L138.415,517.117L137.179,518.175V519.851L135.943,518.881L134.442,519.41L134.971,517.822L134,516.411H135.589L136.473,515Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M314.454,410.406L314.896,412.082L316.397,412.523L315.16,413.493V415.257L313.924,414.199L312.423,414.816L312.953,413.14L311.981,411.817H313.571L314.454,410.406Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M344.212,488.195L344.742,489.871L346.243,490.4L344.919,491.37L345.007,493.046L343.683,492.076L342.27,492.605L342.711,491.018L341.828,489.606H343.329L344.212,488.195Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M47.067,420.814L47.508,422.402L49.009,422.931L47.773,423.901V425.665L46.537,424.607L45.036,425.136L45.566,423.549L44.594,422.137L46.184,422.226L47.067,420.814Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M265.887,448.42L266.328,450.007L267.829,450.537L266.593,451.507V453.271L265.357,452.212L263.856,452.83L264.385,451.154L263.414,449.831H265.004L265.887,448.42Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M110.912,413.759L111.441,415.435L112.854,415.876L111.618,416.934V418.61L110.382,417.551L108.969,418.169L109.41,416.493L108.439,415.17H110.028L110.912,413.759Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M149.412,418.344L152.238,420.372L155.505,419.138L154.445,422.489L156.565,425.135L153.121,425.223L151.178,428.045L150.118,424.782L146.851,423.812L149.589,421.783L149.412,418.344Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M37.532,450L38.239,452.381L40.358,453.087L38.504,454.498L38.592,456.968L36.737,455.556L34.618,456.35L35.325,453.969L34,452.029H36.208L37.532,450Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M141.906,455.74L142.612,458.122L144.731,458.827L142.877,460.238L142.965,462.708L141.111,461.208L139.08,462.09L139.786,459.709L138.374,457.769H140.669L141.906,455.74Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M201.777,459.267L202.395,461.56L204.603,462.265L202.748,463.765L202.836,466.146L200.982,464.735L198.863,465.529L199.569,463.235L198.245,461.207H200.452L201.777,459.267Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M370.175,504.248L370.793,506.629L373.001,507.335L371.146,508.746L371.234,511.216L369.38,509.716L367.261,510.598L367.967,508.217L366.643,506.277H368.85L370.175,504.248Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M300.532,437L301.239,439.381L303.358,440.087L301.592,441.498V443.968L299.826,442.468L297.706,443.35L298.413,440.969L297,439.029H299.296L300.532,437Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M193.384,420L193.914,421.676L195.415,422.117L194.091,423.087L194.179,424.851L192.854,423.792L191.442,424.41L191.883,422.734L191,421.411H192.501L193.384,420Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M248.313,469.323L248.843,470.911L250.256,471.44L249.02,472.41V474.174L247.784,473.116L246.371,473.645L246.812,472.057L245.841,470.646L247.43,470.734L248.313,469.323Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M169.988,496.751L170.518,498.427L172.019,498.956L170.694,499.926L170.782,501.602L169.458,500.632L168.045,501.161L168.487,499.573L167.604,498.162H169.105L169.988,496.751Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M222.441,400L222.882,401.676L224.384,402.117L223.059,403.175L223.147,404.851L221.911,403.792L220.41,404.41L220.851,402.734L219.968,401.411H221.469L222.441,400Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M211.754,492.341L212.284,493.928L213.785,494.458L212.461,495.428L212.549,497.192L211.225,496.133L209.812,496.662L210.253,495.075L209.37,493.664L210.871,493.752L211.754,492.341Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M254.054,420.814L254.584,422.402L256.085,422.931L254.761,423.901L254.849,425.665L253.524,424.607L252.111,425.136L252.553,423.549L251.67,422.137L253.171,422.226L254.054,420.814Z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M307.162,602.392H205L255.565,502.27H322.273L374.238,602.392H307.162L255.565,502.785L307.162,602.392Z"
android:fillColor="#5F6368"/>
<path
android:pathData="M307.162,602.392H205L255.565,502.27H322.273L374.238,602.392H307.162ZM307.162,602.392L255.565,502.785"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#A8CBFE"/>
<path
android:pathData="M255.565,503.079V602.392"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#A8CBFE"/>
<path
android:pathData="M356.686,430.931C358.218,423.774 353.2,416.635 345.477,414.986C337.754,413.337 330.251,417.801 328.719,424.958C327.186,432.115 332.205,439.254 339.928,440.903C347.651,442.553 355.154,438.088 356.686,430.931Z"
android:fillColor="#FEEFC3"/>
<path
android:pathData="M344.708,427.026C345.789,421.976 342.244,416.939 336.79,415.774C331.336,414.609 326.038,417.759 324.957,422.808C323.876,427.858 327.421,432.895 332.875,434.06C338.329,435.225 343.627,432.076 344.708,427.026Z"
android:fillColor="#5F6368"/>
<path
android:pathData="M2,101L410,101A2,2 0,0 1,412 103L412,187A2,2 0,0 1,410 189L2,189A2,2 0,0 1,0 187L0,103A2,2 0,0 1,2 101z"
android:fillColor="#E8EAED"/>
<path
android:pathData="M99,129L313,129A2,2 0,0 1,315 131L315,159A2,2 0,0 1,313 161L99,161A2,2 0,0 1,97 159L97,131A2,2 0,0 1,99 129z"
android:fillColor="#80868B"/>
<path
android:pathData="M32,123L60,123A8,8 0,0 1,68 131L68,159A8,8 0,0 1,60 167L32,167A8,8 0,0 1,24 159L24,131A8,8 0,0 1,32 123z"
android:fillColor="#80868B"/>
<path
android:pathData="M0,0h412v101h-412z"
android:fillColor="#202124"/>
<path
android:pathData="M34.5,48L377.5,48A18.5,18.5 0,0 1,396 66.5L396,66.5A18.5,18.5 0,0 1,377.5 85L34.5,85A18.5,18.5 0,0 1,16 66.5L16,66.5A18.5,18.5 0,0 1,34.5 48z"
android:fillColor="#3C4043"/>
<path
android:pathData="M28,654L356,654A4,4 0,0 1,360 658L360,672A4,4 0,0 1,356 676L28,676A4,4 0,0 1,24 672L24,658A4,4 0,0 1,28 654z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,684L367,684A4,4 0,0 1,371 688L371,702A4,4 0,0 1,367 706L28,706A4,4 0,0 1,24 702L24,688A4,4 0,0 1,28 684z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,714L337,714A4,4 0,0 1,341 718L341,732A4,4 0,0 1,337 736L28,736A4,4 0,0 1,24 732L24,718A4,4 0,0 1,28 714z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,744L210,744A4,4 0,0 1,214 748L214,762A4,4 0,0 1,210 766L28,766A4,4 0,0 1,24 762L24,748A4,4 0,0 1,28 744z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,790L344,790A4,4 0,0 1,348 794L348,808A4,4 0,0 1,344 812L28,812A4,4 0,0 1,24 808L24,794A4,4 0,0 1,28 790z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M28,820L337,820A4,4 0,0 1,341 824L341,838A4,4 0,0 1,337 842L28,842A4,4 0,0 1,24 838L24,824A4,4 0,0 1,28 820z"
android:fillColor="#BDC1C6"/>
</vector>

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="360dp"
android:height="146dp"
android:viewportWidth="360"
android:viewportHeight="146">
<path
android:pathData="M25,96L335,96A25,25 0,0 1,360 121L360,121A25,25 0,0 1,335 146L25,146A25,25 0,0 1,0 121L0,121A25,25 0,0 1,25 96z"
android:fillColor="#3C4043"/>
<path
android:pathData="M30,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
android:fillColor="#8AB4F8"/>
<path
android:pathData="M130,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
android:fillColor="#F28B82"/>
<path
android:pathData="M230,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
android:fillColor="#FDD663"/>
<path
android:pathData="M330,30m-30,0a30,30 0,1 1,60 0a30,30 0,1 1,-60 0"
android:fillColor="#81C995"/>
</vector>

View File

@@ -0,0 +1,79 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="892dp"
android:viewportWidth="412"
android:viewportHeight="892">
<group>
<clip-path
android:pathData="M24,0L388,0A24,24 0,0 1,412 24L412,868A24,24 0,0 1,388 892L24,892A24,24 0,0 1,0 868L0,24A24,24 0,0 1,24 0z"/>
<path
android:pathData="M24,0L388,0A24,24 0,0 1,412 24L412,868A24,24 0,0 1,388 892L24,892A24,24 0,0 1,0 868L0,24A24,24 0,0 1,24 0z"
android:fillColor="#000000"/>
<path
android:pathData="M183,464L183,464A46,46 0,0 1,137 510L-81,510A46,46 0,0 1,-127 464L-127,464A46,46 0,0 1,-81 418L137,418A46,46 0,0 1,183 464z"
android:strokeAlpha="0.6"
android:fillColor="#EA4335"
android:fillAlpha="0.3"/>
<path
android:pathData="M91,464C91,489.405 111.595,510 137,510C162.405,510 183,489.405 183,464C183,438.595 162.405,418 137,418C111.595,418 91,438.595 91,464Z"
android:strokeAlpha="0.6"
android:fillColor="#EA4335"
android:fillAlpha="0.6"/>
<path
android:pathData="M100,464C100,484.435 116.565,501 137,501C157.434,501 174,484.435 174,464C174,443.565 157.434,427 137,427C116.565,427 100,443.565 100,464Z"
android:fillColor="#EA4335"/>
<path
android:pathData="M24,0L388,0A24,24 0,0 1,412 24L412,868A24,24 0,0 1,388 892L24,892A24,24 0,0 1,0 868L0,24A24,24 0,0 1,24 0z"
android:fillColor="#000000"/>
<path
android:pathData="M24,0L388,0A24,24 0,0 1,412 24L412,868A24,24 0,0 1,388 892L24,892A24,24 0,0 1,0 868L0,24A24,24 0,0 1,24 0z"
android:fillColor="#000000"/>
<path
android:pathData="M0,101h412v791h-412z"
android:fillColor="#F1F3F4"/>
<path
android:pathData="M0,0h412v101h-412z"
android:fillColor="#202124"/>
<path
android:pathData="M34.5,48L377.5,48A18.5,18.5 0,0 1,396 66.5L396,66.5A18.5,18.5 0,0 1,377.5 85L34.5,85A18.5,18.5 0,0 1,16 66.5L16,66.5A18.5,18.5 0,0 1,34.5 48z"
android:fillColor="#3C4043"/>
<path
android:pathData="M168,875L244,875A2,2 0,0 1,246 877L246,877A2,2 0,0 1,244 879L168,879A2,2 0,0 1,166 877L166,877A2,2 0,0 1,168 875z"
android:fillColor="#373737"/>
<path
android:pathData="M57,355L355,355A25,25 0,0 1,380 380L380,380A25,25 0,0 1,355 405L57,405A25,25 0,0 1,32 380L32,380A25,25 0,0 1,57 355z"
android:strokeWidth="4"
android:fillColor="#DADCE0"
android:strokeColor="#BDC1C6"/>
<path
android:pathData="M104,429L192,429A4,4 0,0 1,196 433L196,449A4,4 0,0 1,192 453L104,453A4,4 0,0 1,100 449L100,433A4,4 0,0 1,104 429z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M220,429L308,429A4,4 0,0 1,312 433L312,449A4,4 0,0 1,308 453L220,453A4,4 0,0 1,216 449L216,433A4,4 0,0 1,220 429z"
android:fillColor="#DADCE0"/>
<path
android:pathData="M120,242L120,242A26,26 0,0 1,146 268L146,268A26,26 0,0 1,120 294L120,294A26,26 0,0 1,94 268L94,268A26,26 0,0 1,120 242z"
android:fillColor="#4285F4"/>
<path
android:pathData="M243,261L243,261A17,17 0,0 1,260 278L260,294A17,17 0,0 1,243 311L243,311A17,17 0,0 1,226 294L226,278A17,17 0,0 1,243 261z"
android:fillColor="#4285F4"/>
<path
android:pathData="M272,244L272,244A8,8 0,0 1,280 252L280,286A8,8 0,0 1,272 294L272,294A8,8 0,0 1,264 286L264,252A8,8 0,0 1,272 244z"
android:fillColor="#34A853"/>
<path
android:pathData="M167,261L167,261A17,17 0,0 1,184 278L184,278A17,17 0,0 1,167 295L167,295A17,17 0,0 1,150 278L150,278A17,17 0,0 1,167 261z"
android:fillColor="#E94235"/>
<path
android:pathData="M301,261L301,261A17,17 0,0 1,318 278L318,278A17,17 0,0 1,301 295L301,295A17,17 0,0 1,284 278L284,278A17,17 0,0 1,301 261z"
android:fillColor="#E94235"/>
<path
android:pathData="M205,261L205,261A17,17 0,0 1,222 278L222,278A17,17 0,0 1,205 295L205,295A17,17 0,0 1,188 278L188,278A17,17 0,0 1,205 261z"
android:fillColor="#FABB05"/>
<path
android:pathData="M34,128L50,128A4,4 0,0 1,54 132L54,148A4,4 0,0 1,50 152L34,152A4,4 0,0 1,30 148L30,132A4,4 0,0 1,34 128z"
android:fillColor="#BDC1C6"/>
<path
android:pathData="M68,131L117,131A4,4 0,0 1,121 135L121,145A4,4 0,0 1,117 149L68,149A4,4 0,0 1,64 145L64,135A4,4 0,0 1,68 131z"
android:fillColor="#BDC1C6"/>
</group>
</vector>

View File

@@ -16,13 +16,16 @@
<com.android.quickstep.interaction.RootSandboxLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:clipChildren="false">
<View
android:id="@+id/gesture_tutorial_ripple_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gesture_tutorial_ripple"/>
<ImageView
android:id="@+id/gesture_tutorial_fake_launcher_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="70dp" />
<com.android.launcher3.views.ClipIconView
android:id="@+id/gesture_tutorial_fake_icon_view"
@@ -42,13 +45,22 @@
android:id="@+id/gesture_tutorial_fake_task_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
android:visibility="visible" />
<ImageView
android:id="@+id/gesture_tutorial_fragment_hand_coaching"
<View
android:id="@+id/gesture_tutorial_ripple_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
android:background="@drawable/gesture_tutorial_ripple"/>
<VideoView
android:id="@+id/gesture_tutorial_feedback_video"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"/>
<ImageButton
android:id="@+id/gesture_tutorial_fragment_close_button"
@@ -63,7 +75,8 @@
android:contentDescription="@string/gesture_tutorial_close_button_content_description"
android:padding="18dp"
android:src="@drawable/gesture_tutorial_close_button"
android:tint="?android:attr/textColorPrimary"/>
android:tint="?android:attr/textColorPrimary"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/gesture_tutorial_fragment_titles_container"
@@ -93,16 +106,33 @@
android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"/>
</LinearLayout>
<TextView
<LinearLayout
android:id="@+id/gesture_tutorial_fragment_feedback_view"
style="@style/TextAppearance.GestureTutorial.Feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/gesture_tutorial_fragment_titles_container"
android:orientation="vertical"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
android:layout_marginTop="40dp"/>
android:layout_marginTop="24dp"
android:padding="24dp"
android:gravity="center_vertical"
android:background="@drawable/bg_sandbox_feedback">
<TextView
android:id="@+id/gesture_tutorial_fragment_feedback_title"
style="@style/TextAppearance.GestureTutorial.Feedback.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"/>
<TextView
android:id="@+id/gesture_tutorial_fragment_feedback_subtitle"
style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
of elevation and shadow) which is replaced by ripple effect in android:foreground -->

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -47,12 +47,24 @@
<item name="android:textSize">21sp</item>
</style>
<style name="TextAppearance.GestureTutorial.Feedback"
<style name="TextAppearance.GestureTutorial.Feedback.Title"
parent="TextAppearance.GestureTutorial">
<item name="android:gravity">center</item>
<item name="android:gravity">start</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">google-sans-display</item>
<item name="android:letterSpacing">0.03</item>
<item name="android:textSize">21sp</item>
<item name="android:textSize">36sp</item>
<item name="android:lineHeight">42sp</item>
</style>
<style name="TextAppearance.GestureTutorial.Feedback.Subtitle"
parent="TextAppearance.GestureTutorial">
<item name="android:gravity">start</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">google-sans</item>
<item name="android:letterSpacing">0.03</item>
<item name="android:textSize">18sp</item>
<item name="android:lineHeight">24sp</item>
</style>
<style name="TextAppearance.GestureTutorial.ButtonLabel"

View File

@@ -18,7 +18,6 @@ package com.android.quickstep.interaction;
import static com.android.quickstep.interaction.TutorialController.TutorialType.ASSISTANT_COMPLETE;
import android.graphics.PointF;
import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
@@ -59,11 +58,6 @@ final class AssistantGestureTutorialController extends TutorialController {
return null;
}
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
@@ -101,8 +95,7 @@ final class AssistantGestureTutorialController extends TutorialController {
showFeedback(R.string.assistant_gesture_feedback_swipe_too_far_from_corner);
break;
case ASSISTANT_COMPLETED:
hideFeedback();
hideHandCoachingAnimation();
hideFeedback(true);
showRippleEffect(
() -> {
if (mTutorialFragment.isTutorialComplete()) {

View File

@@ -18,16 +18,10 @@ package com.android.quickstep.interaction;
import android.view.MotionEvent;
import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Home gesture interactive tutorial. */
public class AssistantGestureTutorialFragment extends TutorialFragment {
@Override
Integer getHandAnimationResId() {
return R.drawable.assistant_gesture;
}
@Override
TutorialController createController(TutorialType type) {
return new AssistantGestureTutorialController(this, type);

View File

@@ -17,10 +17,14 @@ package com.android.quickstep.interaction;
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
import static com.android.quickstep.interaction.TutorialController.TutorialType.LEFT_EDGE_BACK_NAVIGATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
import android.graphics.PointF;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
@@ -72,16 +76,17 @@ final class BackGestureTutorialController extends TutorialController {
return null;
}
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
@Override
void onActionTextButtonClicked(View button) {
mTutorialFragment.startSystemNavigationSetting();
}
@Nullable
@Override
public View getMockLauncherView() {
return null;
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
@@ -103,10 +108,12 @@ final class BackGestureTutorialController extends TutorialController {
private void handleAttemptFromRight(BackGestureResult result) {
switch (result) {
case BACK_COMPLETED_FROM_RIGHT:
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION));
hideFeedback(true);
mFakeTaskView.setBackground(AppCompatResources.getDrawable(mContext,
R.drawable.sandbox_fake_google_search));
showRippleEffect(null);
showFeedback(R.string.back_gesture_feedback_complete,
mTutorialFragment::continueTutorial);
break;
case BACK_CANCELLED_FROM_RIGHT:
showFeedback(R.string.back_gesture_feedback_cancelled_right_edge);
@@ -125,16 +132,12 @@ final class BackGestureTutorialController extends TutorialController {
private void handleAttemptFromLeft(BackGestureResult result) {
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
hideFeedback();
hideHandCoachingAnimation();
showRippleEffect(
() -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
hideFeedback(true);
mFakeTaskView.setBackground(AppCompatResources.getDrawable(mContext,
R.drawable.sandbox_fake_google_search));
showRippleEffect(null);
showFeedback(R.string.back_gesture_feedback_complete_left_edge,
() -> mTutorialFragment.changeController(RIGHT_EDGE_BACK_NAVIGATION));
break;
case BACK_CANCELLED_FROM_LEFT:
showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
@@ -156,6 +159,9 @@ final class BackGestureTutorialController extends TutorialController {
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
mTutorialFragment.closeTutorial();
}
} else if (mTutorialType == LEFT_EDGE_BACK_NAVIGATION
|| mTutorialType == RIGHT_EDGE_BACK_NAVIGATION) {
showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
}
}
}

View File

@@ -18,14 +18,19 @@ package com.android.quickstep.interaction;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Back gesture interactive tutorial. */
public class BackGestureTutorialFragment extends TutorialFragment {
@Nullable
@Override
Integer getHandAnimationResId() {
return R.drawable.back_gesture;
Integer getFeedbackVideoResId() {
return mTutorialType == TutorialType.RIGHT_EDGE_BACK_NAVIGATION
? R.raw.tips_nav_back_right
: R.raw.tips_nav_back_left;
}
@Override

View File

@@ -145,6 +145,7 @@ public class GestureSandboxActivity extends FragmentActivity {
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}

View File

@@ -20,7 +20,6 @@ import static com.android.quickstep.interaction.TutorialController.TutorialType.
import android.annotation.TargetApi;
import android.graphics.PointF;
import android.os.Build;
import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
@@ -61,11 +60,6 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
return null;
}
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
@@ -90,17 +84,16 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
if (mHideFeedbackEndAction != null) {
return;
}
switch (mTutorialType) {
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
animateFakeTaskViewHome(finalVelocity, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
animateFakeTaskViewHome(finalVelocity, null);
showFeedback(R.string.home_gesture_feedback_complete,
mTutorialFragment::continueTutorial);
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
@@ -108,12 +101,12 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () ->
fadeOutFakeTaskView(true, true, () ->
showFeedback(R.string.home_gesture_feedback_overview_detected));
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:
fadeOutFakeTaskView(false, null);
fadeOutFakeTaskView(false, true, null);
showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
break;
}

View File

@@ -15,14 +15,17 @@
*/
package com.android.quickstep.interaction;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Home gesture interactive tutorial. */
public class HomeGestureTutorialFragment extends TutorialFragment {
@Nullable
@Override
Integer getHandAnimationResId() {
return R.drawable.home_gesture;
Integer getFeedbackVideoResId() {
return R.raw.tips_nav_home;
}
@Override

View File

@@ -15,14 +15,21 @@
*/
package com.android.quickstep.interaction;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.graphics.PointF;
import android.os.Build;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SwipeUpAnimationLogic;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
@@ -62,9 +69,10 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
return null;
}
@Nullable
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
public View getMockLauncherView() {
return null;
}
@Override
@@ -91,12 +99,17 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
if (mHideFeedbackEndAction != null) {
return;
}
switch (mTutorialType) {
case OVERVIEW_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
animateFakeTaskViewHome(finalVelocity, () ->
showFeedback(R.string.overview_gesture_feedback_home_detected));
animateFakeTaskViewHome(finalVelocity, () -> {
resetFakeTaskView();
showFeedback(R.string.overview_gesture_feedback_home_detected);
});
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
@@ -104,17 +117,19 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () -> {
if (mTutorialFragment.isTutorialComplete()) {
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE);
} else {
mTutorialFragment.continueTutorial();
}
});
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
AnimatorSet animset = anim.buildAnim();
animset.start();
mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset);
onMotionPaused(true /*arbitrary value*/);
showFeedback(R.string.overview_gesture_feedback_complete,
mTutorialFragment::continueTutorial);
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:
fadeOutFakeTaskView(false, null);
fadeOutFakeTaskView(false, true, null);
showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction);
break;
}

View File

@@ -15,14 +15,17 @@
*/
package com.android.quickstep.interaction;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Overview gesture interactive tutorial. */
public class OverviewGestureTutorialFragment extends TutorialFragment {
@Nullable
@Override
Integer getHandAnimationResId() {
return R.drawable.overview_gesture;
Integer getFeedbackVideoResId() {
return R.raw.tips_nav_overview;
}
@Override

View File

@@ -16,7 +16,6 @@
package com.android.quickstep.interaction;
import android.graphics.PointF;
import android.view.View;
import androidx.annotation.Nullable;
@@ -49,11 +48,6 @@ public class SandboxModeTutorialController extends SwipeUpGestureTutorialControl
return null;
}
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
switch (result) {
@@ -87,7 +81,7 @@ public class SandboxModeTutorialController extends SwipeUpGestureTutorialControl
});
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () -> {
fadeOutFakeTaskView(true, true, () -> {
showFeedback(R.string.sandbox_mode_overview_gesture_feedback_successful);
});
break;

View File

@@ -27,17 +27,15 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -56,7 +54,6 @@ import com.android.quickstep.SwipeUpAnimationLogic;
import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@TargetApi(Build.VERSION_CODES.R)
@@ -64,13 +61,37 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
private final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
private float mFakeTaskViewRadius;
private Rect mFakeTaskViewRect = new Rect();
private RunningWindowAnim mRunningWindowAnim;
RunningWindowAnim mRunningWindowAnim;
private boolean mShowTasks = false;
private boolean mShowPreviousTasks = false;
private AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mFakeLauncherView.setVisibility(View.INVISIBLE);
mFakeIconView.setVisibility(View.INVISIBLE);
if (mTutorialFragment.getActivity() != null) {
DisplayMetrics displayMetrics =
mTutorialFragment.getResources().getDisplayMetrics();
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
mFakeTaskViewRect.set(0, 0, width, height);
}
mFakeTaskViewRadius = 0;
mFakeTaskView.invalidateOutline();
mFakeTaskView.setVisibility(View.VISIBLE);
mFakeTaskView.setAlpha(1);
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
mFakePreviousTaskView.setAlpha(1);
mShowTasks = false;
mShowPreviousTasks = false;
mRunningWindowAnim = null;
}
};
SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
super(tutorialFragment, tutorialType);
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
@@ -83,14 +104,13 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext)
.copy(mContext);
Insets insets = mContext.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getWindowInsets()
.getInsets(WindowInsets.Type.systemBars());
dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
mTaskViewSwipeUpAnimation.initDp(dp);
mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
mFakeTaskViewRect.set(0, 0, width, height);
mFakeTaskViewRadius = 0;
ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
@Override
@@ -114,24 +134,11 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
}
/** Fades the task view, optionally after animating to a fake Overview. */
void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
hideFeedback();
hideHandCoachingAnimation();
void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset,
@Nullable Runnable onEndRunnable) {
hideFeedback(true);
cancelRunningAnimation();
PendingAnimation anim = new PendingAnimation(300);
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
mFakeIconView.setVisibility(View.INVISIBLE);
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
mFakePreviousTaskView.setAlpha(1);
mShowTasks = false;
mShowPreviousTasks = false;
mRunningWindowAnim = null;
}
};
if (toOverviewFirst) {
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
@@ -139,9 +146,17 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
PendingAnimation fadeAnim = new PendingAnimation(300);
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
fadeAnim.addListener(resetTaskView);
if (reset) {
fadeAnim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
fadeAnim.addListener(mResetTaskView);
} else {
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
}
if (onEndRunnable != null) {
fadeAnim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
}
AnimatorSet animset = fadeAnim.buildAnim();
animset.setStartDelay(100);
animset.start();
@@ -149,36 +164,62 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
}
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
anim.setViewAlpha(mFakeIconView, 0, ACCEL);
anim.addListener(resetTaskView);
}
if (onEndRunnable != null) {
anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
if (reset) {
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
anim.addListener(mResetTaskView);
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
}
if (onEndRunnable != null) {
anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
}
}
AnimatorSet animset = anim.buildAnim();
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
void resetFakeTaskView() {
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
anim.setViewAlpha(mFakeTaskView, 1, ACCEL);
anim.addListener(mResetTaskView);
AnimatorSet animset = anim.buildAnim();
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
hideFeedback();
hideHandCoachingAnimation();
hideFeedback(true);
cancelRunningAnimation();
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
mFakeLauncherView.setVisibility(View.VISIBLE);
mShowPreviousTasks = false;
RectFSpringAnim rectAnim =
mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and run onEndRunnable.
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
() -> fadeOutFakeTaskView(false, onEndRunnable)));
PendingAnimation fadeAnim = new PendingAnimation(300);
fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL);
if (onEndRunnable != null) {
fadeAnim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
}
AnimatorSet animset = fadeAnim.buildAnim();
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(animset::start));
mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
}
@Override
public void setNavBarGestureProgress(@Nullable Float displacement) {
if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
if (mHideFeedbackEndAction != null) {
return;
}
if (displacement != null) {
hideFeedback(true);
}
if (mTutorialType == HOME_NAVIGATION_COMPLETE
|| mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
@@ -188,7 +229,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
if (mShowPreviousTasks) {
mFakePreviousTaskView.setVisibility(View.VISIBLE);
}
if (mRunningWindowAnim == null) {
if (mRunningWindowAnim == null && displacement != null) {
mTaskViewSwipeUpAnimation.updateDisplacement(displacement);
}
}
@@ -196,6 +237,9 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
@Override
public void onMotionPaused(boolean unused) {
if (mHideFeedbackEndAction != null) {
return;
}
if (mShowTasks) {
if (!mShowPreviousTasks) {
mFakePreviousTaskView.setTranslationX(
@@ -253,9 +297,9 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
@NonNull
@Override
public RectF getWindowTargetRect() {
int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
int fakeHomeIconSizePx = Utilities.dpToPx(60);
int fakeHomeIconLeft = mFakeLauncherView.getLeft();
int fakeHomeIconTop = mDp.heightPx - Utilities.dpToPx(216);
return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
fakeHomeIconLeft + fakeHomeIconSizePx,
fakeHomeIconTop + fakeHomeIconSizePx);

View File

@@ -19,17 +19,21 @@ import android.content.Context;
import android.graphics.drawable.RippleDrawable;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.VideoView;
import androidx.annotation.CallSuper;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -37,8 +41,8 @@ import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttem
abstract class TutorialController implements BackGestureAttemptCallback,
NavBarGestureAttemptCallback {
private static final int FEEDBACK_VISIBLE_MS = 3000;
private static final int FEEDBACK_ANIMATION_MS = 500;
private static final int FEEDBACK_VISIBLE_MS = 2500;
private static final int FEEDBACK_ANIMATION_MS = 250;
private static final int RIPPLE_VISIBLE_MS = 300;
final TutorialFragment mTutorialFragment;
@@ -48,18 +52,18 @@ abstract class TutorialController implements BackGestureAttemptCallback,
final ImageButton mCloseButton;
final TextView mTitleTextView;
final TextView mSubtitleTextView;
final TextView mFeedbackView;
final View mLauncherView;
final ViewGroup mFeedbackView;
final VideoView mFeedbackVideoView;
final ImageView mFakeLauncherView;
final ClipIconView mFakeIconView;
final View mFakeTaskView;
final View mFakePreviousTaskView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
@Nullable final TutorialHandAnimation mHandCoachingAnimation;
final ImageView mHandCoachingView;
final Button mActionTextButton;
final Button mActionButton;
private final Runnable mHideFeedbackRunnable;
Runnable mHideFeedbackEndAction;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -72,34 +76,23 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mLauncherView = getMockLauncherView();
mFeedbackVideoView = rootView.findViewById(R.id.gesture_tutorial_feedback_video);
mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view);
mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
mFakePreviousTaskView =
rootView.findViewById(R.id.gesture_tutorial_fake_previous_task_view);
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
mHandCoachingAnimation = tutorialFragment.getHandAnimation();
mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
mHandCoachingView.bringToFront();
mActionTextButton =
rootView.findViewById(R.id.gesture_tutorial_fragment_action_text_button);
mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
mHideFeedbackRunnable =
() -> mFeedbackView.animate().alpha(0).setDuration(FEEDBACK_ANIMATION_MS)
.withEndAction(this::showHandCoachingAnimation).start();
if (mLauncherView != null) {
rootView.addView(mLauncherView, 0);
}
if (mContext != null) {
rootView.setBackground(mContext.getDrawable(getMockWallpaperResId()));
mFakeTaskView.setBackground(mContext.getDrawable(getMockAppTaskThumbnailResId()));
mFakePreviousTaskView.setBackground(
mContext.getDrawable(getMockPreviousAppTaskThumbnailResId()));
mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
}
() -> mFeedbackView.animate()
.translationY(-mFeedbackView.getTop() - mFeedbackView.getHeight())
.setDuration(FEEDBACK_ANIMATION_MS)
.withEndAction(this::hideFeedbackEndAction).start();
}
void setTutorialType(TutorialType tutorialType) {
@@ -126,6 +119,11 @@ abstract class TutorialController implements BackGestureAttemptCallback,
return null;
}
@DrawableRes
protected int getMockLauncherResId() {
return R.drawable.default_sandbox_mock_launcher;
}
@DrawableRes
protected int getMockAppTaskThumbnailResId() {
return R.drawable.default_sandbox_app_task_thumbnail;
@@ -153,19 +151,76 @@ abstract class TutorialController implements BackGestureAttemptCallback,
return R.drawable.default_sandbox_wallpaper;
}
void showFeedback(int resId) {
hideHandCoachingAnimation();
mFeedbackView.setText(resId);
mFeedbackView.animate().alpha(1).setDuration(FEEDBACK_ANIMATION_MS).start();
mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
mFeedbackView.postDelayed(mHideFeedbackRunnable, FEEDBACK_VISIBLE_MS);
void fadeTaskViewAndRun(Runnable r) {
mFakeTaskView.animate().alpha(0).setListener(AnimationSuccessListener.forRunnable(r));
}
void hideFeedback() {
mFeedbackView.setText(null);
/**
* Show feedback reflecting a failed gesture attempt.
*
* @param subtitleResId Resource of the text to display.
**/
void showFeedback(int subtitleResId) {
showFeedback(subtitleResId, null);
}
/**
* Show feedback reflecting the result of a gesture attempt.
*
* @param successEndAction Non-null iff the gesture was successful; this is run after the
* feedback is shown (i.e. to go to the next step)
**/
void showFeedback(int subtitleResId, @Nullable Runnable successEndAction) {
if (mHideFeedbackEndAction != null) {
return;
}
int visibleDuration = FEEDBACK_VISIBLE_MS;
if (mTutorialFragment.getFeedbackVideoResId() != null) {
if (successEndAction == null) {
if (mFeedbackVideoView.isPlaying()) {
mFeedbackVideoView.seekTo(1);
} else {
mFeedbackVideoView.start();
}
mFeedbackVideoView.setVisibility(View.VISIBLE);
visibleDuration = mTutorialFragment.getFeedbackVideoDuration();
} else {
mTutorialFragment.releaseFeedbackVideoView();
}
}
TextView title = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_title);
title.setText(successEndAction == null
? R.string.gesture_tutorial_try_again
: R.string.gesture_tutorial_nice);
TextView subtitle =
mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle);
subtitle.setText(subtitleResId);
mHideFeedbackEndAction = successEndAction;
mFeedbackView.setTranslationY(-mFeedbackView.getHeight() - mFeedbackView.getTop());
mFeedbackView.setVisibility(View.VISIBLE);
mFeedbackView.animate()
.setDuration(FEEDBACK_ANIMATION_MS)
.translationY(0)
.start();
mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
mFeedbackView.postDelayed(mHideFeedbackRunnable, visibleDuration);
}
void hideFeedback(boolean releaseFeedbackVideo) {
mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
mHideFeedbackEndAction = null;
mFeedbackView.clearAnimation();
mFeedbackView.setAlpha(0);
mFeedbackView.setVisibility(View.INVISIBLE);
if (releaseFeedbackVideo) {
mTutorialFragment.releaseFeedbackVideoView();
}
}
void hideFeedbackEndAction() {
if (mHideFeedbackEndAction != null) {
mHideFeedbackEndAction.run();
mHideFeedbackEndAction = null;
}
}
void setRippleHotspot(float x, float y) {
@@ -183,38 +238,21 @@ abstract class TutorialController implements BackGestureAttemptCallback,
}, RIPPLE_VISIBLE_MS);
}
void onActionButtonClicked(View button) {}
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
void onActionTextButtonClicked(View button) {}
void showHandCoachingAnimation() {
if (isComplete() || mHandCoachingAnimation == null) {
return;
}
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
}
void hideHandCoachingAnimation() {
if (mHandCoachingAnimation == null) {
return;
}
mHandCoachingAnimation.stop();
mHandCoachingView.setVisibility(View.INVISIBLE);
}
@CallSuper
void transitToController() {
hideFeedback();
hideFeedback(false);
updateTitles();
updateActionButtons();
updateDrawables();
if (isComplete()) {
hideHandCoachingAnimation();
} else {
showHandCoachingAnimation();
}
if (mLauncherView != null) {
mLauncherView.setVisibility(isComplete() ? View.INVISIBLE : View.VISIBLE);
if (mFakeLauncherView != null) {
mFakeLauncherView.setVisibility(View.INVISIBLE);
}
}
@@ -253,6 +291,24 @@ abstract class TutorialController implements BackGestureAttemptCallback,
button.setOnClickListener(listener);
}
private void updateDrawables() {
if (mContext != null) {
mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
mContext, getMockWallpaperResId()));
mTutorialFragment.updateFeedbackVideo();
mFakeLauncherView.setImageDrawable(AppCompatResources.getDrawable(
mContext, getMockLauncherResId()));
mFakeTaskView.setBackground(AppCompatResources.getDrawable(
mContext, getMockAppTaskThumbnailResId()));
mFakeTaskView.animate().alpha(1).setListener(AnimationSuccessListener.forRunnable(
() -> mFakeTaskView.animate().cancel()));
mFakePreviousTaskView.setBackground(AppCompatResources.getDrawable(
mContext, getMockPreviousAppTaskThumbnailResId()));
mFakeIconView.setBackground(AppCompatResources.getDrawable(
mContext, getMockAppIconResId()));
}
}
private boolean isComplete() {
return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE
|| mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE

View File

@@ -15,8 +15,10 @@
*/
package com.android.quickstep.interaction;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Insets;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -25,6 +27,7 @@ import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -32,6 +35,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.quickstep.interaction.TutorialController.TutorialType;
abstract class TutorialFragment extends Fragment implements OnTouchListener {
@@ -42,9 +46,10 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
TutorialType mTutorialType;
@Nullable TutorialController mTutorialController = null;
RootSandboxLayout mRootView;
@Nullable TutorialHandAnimation mHandCoachingAnimation = null;
EdgeBackGestureHandler mEdgeBackGestureHandler;
NavBarGestureHandler mNavBarGestureHandler;
private VideoView mFeedbackVideoView;
private int mFeedbackVideoDuration;
public static TutorialFragment newInstance(TutorialType tutorialType) {
TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
@@ -83,7 +88,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return null;
}
@Nullable Integer getHandAnimationResId() {
@Nullable Integer getFeedbackVideoResId() {
return null;
}
@@ -120,29 +125,66 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return insets;
});
mRootView.setOnTouchListener(this);
Integer handAnimationResId = getHandAnimationResId();
if (handAnimationResId != null) {
mHandCoachingAnimation =
new TutorialHandAnimation(getContext(), mRootView, handAnimationResId);
}
mFeedbackVideoView = mRootView.findViewById(R.id.gesture_tutorial_feedback_video);
return mRootView;
}
@Override
public void onStart() {
super.onStart();
initializeFeedbackVideoView();
}
@Override
public void onStop() {
super.onStop();
releaseFeedbackVideoView();
}
void initializeFeedbackVideoView() {
if (!updateFeedbackVideo()) {
mFeedbackVideoView.setVisibility(View.INVISIBLE);
return;
} else {
mFeedbackVideoView.setVisibility(View.VISIBLE);
}
int heightPixels = getResources().getDisplayMetrics().heightPixels;
int heightPixelsWithMargin = heightPixels + Utilities.dpToPx(80);
int widthPixels = getResources().getDisplayMetrics().widthPixels - Utilities.dpToPx(12);
mFeedbackVideoView.setScaleY((float) heightPixelsWithMargin / heightPixels);
mFeedbackVideoView.setScaleX((float) heightPixelsWithMargin / widthPixels);
mFeedbackVideoView.start();
mFeedbackVideoView.setOnPreparedListener(
mp -> mFeedbackVideoDuration = mFeedbackVideoView.getDuration());
mFeedbackVideoView.setOnCompletionListener(mp -> releaseFeedbackVideoView());
}
boolean updateFeedbackVideo() {
Integer feedbackVideoResId = getFeedbackVideoResId();
if (feedbackVideoResId == null || getContext() == null) {
return false;
}
Uri uri = Uri.parse("android.resource://" + getContext().getPackageName() + "/"
+ feedbackVideoResId);
mFeedbackVideoView.setVideoURI(uri);
return true;
}
void releaseFeedbackVideoView() {
mFeedbackVideoView.stopPlayback();
mFeedbackVideoView.setVisibility(View.INVISIBLE);
}
int getFeedbackVideoDuration() {
return mFeedbackVideoDuration;
}
@Override
public void onResume() {
super.onResume();
changeController(mTutorialType);
}
@Override
public void onPause() {
super.onPause();
if (mHandCoachingAnimation != null) {
mHandCoachingAnimation.stop();
}
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Note: Using logical-or to ensure both functions get called.
@@ -167,10 +209,11 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
void changeController(TutorialType tutorialType) {
if (getControllerClass().isInstance(mTutorialController)) {
mTutorialController.setTutorialType(tutorialType);
mTutorialController.fadeTaskViewAndRun(mTutorialController::transitToController);
} else {
mTutorialController = createController(tutorialType);
mTutorialController.transitToController();
}
mTutorialController.transitToController();
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
mTutorialType = tutorialType;
@@ -186,10 +229,6 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return mRootView;
}
@Nullable TutorialHandAnimation getHandAnimation() {
return mHandCoachingAnimation;
}
void continueTutorial() {
if (!(getContext() instanceof GestureSandboxActivity)) {
closeTutorial();
@@ -207,6 +246,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
void closeTutorial() {
FragmentActivity activity = getActivity();
if (activity != null) {
activity.setResult(Activity.RESULT_OK);
activity.finish();
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.interaction;
import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.time.Duration;
/** Hand coaching animation. */
final class TutorialHandAnimation {
// A delay for waiting the Activity fully launches.
private static final Duration ANIMATION_START_DELAY = Duration.ofMillis(300L);
private final ImageView mHandCoachingView;
private final AnimatedVectorDrawable mGestureAnimation;
TutorialHandAnimation(Context context, View rootView, int resId) {
mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context, resId);
}
/** [Re]starts animation for the given tutorial. */
void startLoopedAnimation(TutorialType tutorialType) {
mHandCoachingView.setVisibility(View.VISIBLE);
if (mGestureAnimation.isRunning()) {
stop();
}
mGestureAnimation.clearAnimationCallbacks();
mGestureAnimation.registerAnimationCallback(
new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
mGestureAnimation.start();
}
});
start(tutorialType);
}
private void start(TutorialType tutorialType) {
// Because the gesture animation has only the right side form.
// The left side form of the gesture animation is made from flipping the View.
float rotationY = tutorialType == TutorialType.LEFT_EDGE_BACK_NAVIGATION ? 180f : 0f;
mHandCoachingView.setRotationY(rotationY);
mHandCoachingView.setImageDrawable(mGestureAnimation);
mHandCoachingView.postDelayed(mGestureAnimation::start, ANIMATION_START_DELAY.toMillis());
}
void stop() {
mGestureAnimation.clearAnimationCallbacks();
mGestureAnimation.stop();
}
}

View File

@@ -37,7 +37,7 @@
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_previous_task_view_color">#9CCC65</color> <!-- Light Green -->
<color name="gesture_tutorial_fake_previous_task_view_color">#3C4043</color> <!-- Gray -->
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
<color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->

View File

@@ -276,7 +276,7 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"RIGHT_EDGE_BACK_NAVIGATION"}));
new String[] {"LEFT_EDGE_BACK_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchBackTutorialPreference);