diff options
Diffstat (limited to 'docs')
24 files changed, 2160 insertions, 261 deletions
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd index cfb65a5..52f086e 100644 --- a/docs/html/about/dashboards/index.jd +++ b/docs/html/about/dashboards/index.jd @@ -57,7 +57,7 @@ Platform Versions</a>.</p> </div> -<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015. +<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015. <br/>Any versions with less than 0.1% distribution are not shown.</em> </p> @@ -88,7 +88,7 @@ Screens</a>.</p> </div> -<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015. +<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015. <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p> @@ -108,8 +108,7 @@ support for any lower version (for example, support for version 2.0 also implies <img alt="" style="float:right" -src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chf=bg%2Cs%2C00000000&chd=t%3A67.5%2C32.5&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" /> - +src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0%7CGL%203.1&chf=bg%2Cs%2C00000000&chd=t%3A65.9%2C33.8%2C0.3&chco=c4df9b%2C6fad0c&cht=p&chs=400x250"> <p>To declare which version of OpenGL ES your application requires, you should use the {@code android:glEsVersion} attribute of the <a @@ -127,17 +126,21 @@ uses.</p> </tr> <tr> <td>2.0</td> -<td>67.5%</td> +<td>65.9%</td> </tr> <tr> <td>3.0</td> -<td>32.5%</td> +<td>33.8%</td> +</tr> +<tr> +<td>3.1</td> +<td>0.3%</td> </tr> </table> -<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015</em></p> +<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015</em></p> @@ -155,7 +158,7 @@ uses.</p> var VERSION_DATA = [ { - "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop&chco=c4df9b%2C6fad0c&chd=t%3A0.4%2C6.9%2C5.9%2C42.6%2C40.9%2C3.3&chf=bg%2Cs%2C00000000&chs=500x250&cht=p", + "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop&chf=bg%2Cs%2C00000000&chd=t%3A0.4%2C6.4%2C5.7%2C40.7%2C41.4%2C5.4&chco=c4df9b%2C6fad0c&chs=500x250&cht=p", "data": [ { "api": 8, @@ -165,37 +168,42 @@ var VERSION_DATA = { "api": 10, "name": "Gingerbread", - "perc": "6.9" + "perc": "6.4" }, { "api": 15, "name": "Ice Cream Sandwich", - "perc": "5.9" + "perc": "5.7" }, { "api": 16, "name": "Jelly Bean", - "perc": "17.3" + "perc": "16.5" }, { "api": 17, "name": "Jelly Bean", - "perc": "19.4" + "perc": "18.6" }, { "api": 18, "name": "Jelly Bean", - "perc": "5.9" + "perc": "5.6" }, { "api": 19, "name": "KitKat", - "perc": "40.9" + "perc": "41.4" }, { "api": 21, "name": "Lollipop", - "perc": "3.3" + "perc": "5.0" + }, + { + "api": 22, + "name": "Lollipop", + "perc": "0.4" } ] } @@ -208,29 +216,29 @@ var SCREEN_DATA = "data": { "Large": { "hdpi": "0.6", - "ldpi": "0.5", - "mdpi": "5.1", - "tvdpi": "2.3", + "ldpi": "0.4", + "mdpi": "4.8", + "tvdpi": "2.2", "xhdpi": "0.6" }, "Normal": { - "hdpi": "38.7", - "mdpi": "8.4", + "hdpi": "39.3", + "mdpi": "8.1", "tvdpi": "0.1", - "xhdpi": "18.9", - "xxhdpi": "15.8" + "xhdpi": "19.5", + "xxhdpi": "15.9" }, "Small": { - "ldpi": "4.6" + "ldpi": "4.4" }, "Xlarge": { "hdpi": "0.3", - "mdpi": "3.5", + "mdpi": "3.2", "xhdpi": "0.6" } }, - "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chco=c4df9b%2C6fad0c&chd=t%3A5.1%2C17.0%2C2.4%2C39.6%2C20.1%2C15.8&chf=bg%2Cs%2C00000000&chs=400x250&cht=p", - "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chco=c4df9b%2C6fad0c&chd=t%3A4.4%2C9.1%2C81.9%2C4.6&chf=bg%2Cs%2C00000000&chs=400x250&cht=p" + "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A4.8%2C16.1%2C2.3%2C40.2%2C20.7%2C15.9&chco=c4df9b%2C6fad0c&chs=400x250&cht=p", + "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.1%2C8.6%2C82.9%2C4.4&chco=c4df9b%2C6fad0c&chs=400x250&cht=p" } ]; @@ -312,6 +320,11 @@ var VERSION_NAMES = "api":21, "link":"<a href='/about/versions/android-5.0.html'>5.0</a>", "codename":"Lollipop" + }, + { + "api":22, + "link":"<a href='/about/versions/android-5.1.html'>5.1</a>", + "codename":"Lollipop" } ]; diff --git a/docs/html/google/gcm/c2dm.jd b/docs/html/google/gcm/c2dm.jd index 6ae7c1a..bc58e66 100644 --- a/docs/html/google/gcm/c2dm.jd +++ b/docs/html/google/gcm/c2dm.jd @@ -33,7 +33,10 @@ page.title=Migration </div> </div> -<p>Android Cloud to Device Messaging (C2DM) is deprecated. The C2DM service will continue to be maintained in the short term, but C2DM will accept no new users, and it will grant no new quotas. <strong>C2DM developers are strongly encouraged to move to Google Cloud Messaging (GCM)</strong>. GCM is the next generation of C2DM.</p> +<p>Android Cloud to Device Messaging (C2DM) was officially deprecated on June 26, 2012, and will be + shut down completely as of July 30, 2015. <strong>C2DM developers are strongly encouraged to move + to Google Cloud Messaging (GCM)</strong>. GCM is the next generation of C2DM.</p> + <p>This document is addressed to C2DM developers who are moving to GCM. It describes the differences between GCM and C2DM, and explains how to migrate existing C2DM apps to GCM.</p> diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd index 3f71d04..70e7107 100644 --- a/docs/html/google/play-services/setup.jd +++ b/docs/html/google/play-services/setup.jd @@ -9,7 +9,7 @@ page.title=Setting Up Google Play Services <h2>In this document</h2> <ol> <li><a href="#Setup">Add Google Play Services to Your Project</a></li> - <li><a href="#Proguard">Create a Proguard Exception</a></li> + <li><a href="#Proguard">Create a ProGuard Exception</a></li> <li><a href="#ensure">Ensure Devices Have the Google Play services APK</a></li> </ol> @@ -82,14 +82,6 @@ see <a href="#split">Selectively compiling APIs into your executable</a>. <img src="{@docRoot}images/tools/sync-project.png" style="vertical-align:bottom;margin:0;height:19px" /> in the toolbar. </li> - <li>Open your app's manifest file and add the following tag as a child of the <a -href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> -element: -<pre> -<meta-data android:name="com.google.android.gms.version" - android:value="@integer/google_play_services_version" /> -</pre> - </li> </ol> <p>You can now begin developing features with the @@ -203,6 +195,17 @@ you include an API that does have a separate library.)</p> </tr> </table> +<p class="note"><strong>Note:</strong> ProGuard directives are included in the Play services +client libraries to preserve the required classes. The +<a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plugin for Gradle</a> +automatically appends ProGuard configuration files in an AAR (Android ARchive) package and appends +that package to your ProGuard configuration. During project creation, Android Studio automatically +creates the ProGuard configuration files and <code>build.gradle</code> properties for ProGuard use. +To use ProGuard with Android Studio, you must enable the ProGuard setting in your +<code>build.gradle</code> <code>buildTypes</code>. For more information, see the +<a href="{@docRoot}tools/help/proguard.html">ProGuard</a> topic. </p> + + </div><!-- end studio --> <div class="select-ide eclipse"> @@ -238,6 +241,33 @@ element: you can begin developing features with the <a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p> + +<h2 id="Proguard">Create a ProGuard Exception</h2> + +<p>To prevent <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> from stripping away +required classes, add the following lines in the +<code><project_directory>/proguard-project.txt</code> file: +<pre> +-keep class * extends java.util.ListResourceBundle { + protected Object[][] getContents(); +} + +-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { + public static final *** NULL; +} + +-keepnames @com.google.android.gms.common.annotation.KeepName class * +-keepclassmembernames class * { + @com.google.android.gms.common.annotation.KeepName *; +} + +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR; +} +</pre> + + + </div><!-- end eclipse --> <div class="select-ide other"> @@ -271,8 +301,6 @@ workspace—you should not reference the library directly from the Android S you can begin developing features with the <a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p> -</div><!-- end other --> - <h2 id="Proguard">Create a Proguard Exception</h2> @@ -298,11 +326,9 @@ required classes, add the following lines in the } </pre> -<p class="note"><strong>Note:</strong> When using Android Studio, you must add Proguard -to your <code>build.gradle</code> file's build types. For more information, see the -<a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Running-ProGuard" ->Gradle Plugin User Guide</a>. -</ol> + +</div><!-- end other --> + <h2 id="ensure">Ensure Devices Have the Google Play services APK</h2> diff --git a/docs/html/google/play/billing/billing_integrate.jd b/docs/html/google/play/billing/billing_integrate.jd index e3cacf9..eb58af4 100644 --- a/docs/html/google/play/billing/billing_integrate.jd +++ b/docs/html/google/play/billing/billing_integrate.jd @@ -34,7 +34,7 @@ page.tags="inapp, billing, iap" <h2>See also</h2> <ol> <li><a href="{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a></li> - </ol> + </ol> </div> </div> @@ -42,26 +42,26 @@ page.tags="inapp, billing, iap" <p class="note"><strong>Note:</strong> To see a complete implementation and learn how to test your application, see the <a href="{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a> training class. The training class provides a complete sample In-app Billing application, including convenience classes to handle key tasks related to setting up your connection, sending billing requests and processing responses from Google Play, and managing background threading so that you can make In-app Billing calls from your main activity.</p> -<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with +<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with concepts that will make it easier for you to implement In-app Billing.</p> -<p>To implement In-app Billing in your application, you need to do the +<p>To implement In-app Billing in your application, you need to do the following:</p> <ol> <li>Add the In-app Billing library to your project.</li> <li>Update your {@code AndroidManifest.xml} file.</li> - <li>Create a {@code ServiceConnection} and bind it to + <li>Create a {@code ServiceConnection} and bind it to {@code IInAppBillingService}.</li> - <li>Send In-app Billing requests from your application to + <li>Send In-app Billing requests from your application to {@code IInAppBillingService}.</li> <li>Handle In-app Billing responses from Google Play.</li> </ol> <h2 id="billing-add-aidl">Adding the AIDL file to your project</h2> -<p>{@code IInAppBillingService.aidl} is an Android Interface Definition -Language (AIDL) file that defines the interface to the In-app Billing Version -3 service. You will use this interface to make billing requests by invoking IPC +<p>{@code IInAppBillingService.aidl} is an Android Interface Definition +Language (AIDL) file that defines the interface to the In-app Billing Version +3 service. You will use this interface to make billing requests by invoking IPC method calls.</p> <p>To get the AIDL file:</p> <ol> @@ -76,28 +76,28 @@ method calls.</p> <ol> <li>Copy the {@code IInAppBillingService.aidl} file to your Android project. <ul> - <li>If you are using Eclipse: + <li>If you are using Eclipse: <ol type="a"> - <li>If you are starting from an existing Android project, open the project -in Eclipse. If you are creating a new Android project from scratch, click -<strong>File</strong> > <strong>New</strong> > <strong>Android Application -Project</strong>, then follow the instructions in the <strong>New Android + <li>If you are starting from an existing Android project, open the project +in Eclipse. If you are creating a new Android project from scratch, click +<strong>File</strong> > <strong>New</strong> > <strong>Android Application +Project</strong>, then follow the instructions in the <strong>New Android Application</strong> wizard to create a new project in your workspace.</li> - <li>In the {@code /src} directory, click <strong>File</strong> > + <li>In the {@code /src} directory, click <strong>File</strong> > <strong>New</strong> > <strong>Package</strong>, then create a package named {@code com.android.vending.billing}.</li> - <li>Copy the {@code IInAppBillingService.aidl} file from {@code <sdk>/extras/google/play_billing/} and paste it into the {@code src/com.android.vending.billing/} + <li>Copy the {@code IInAppBillingService.aidl} file from {@code <sdk>/extras/google/play_billing/} and paste it into the {@code src/com.android.vending.billing/} folder in your workspace.</li> </ol> </li> - <li>If you are developing in a non-Eclipse environment: Create the following -directory {@code /src/com/android/vending/billing} and copy the -{@code IInAppBillingService.aidl} file into this directory. Put the AIDL file + <li>If you are developing in a non-Eclipse environment: Create the following +directory {@code /src/com/android/vending/billing} and copy the +{@code IInAppBillingService.aidl} file into this directory. Put the AIDL file into your project and use the Ant tool to build your project so that the <code>IInAppBillingService.java</code> file gets generated.</li> </ul> </li> -<li>Build your application. You should see a generated file named -{@code IInAppBillingService.java} in the {@code /gen} directory of your +<li>Build your application. You should see a generated file named +{@code IInAppBillingService.java} in the {@code /gen} directory of your project.</li> </ol> @@ -135,7 +135,7 @@ ServiceConnection mServiceConn = new ServiceConnection() { } @Override - public void onServiceConnected(ComponentName name, + public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppBillingService.Stub.asInterface(service); } @@ -162,7 +162,7 @@ public void onDestroy() { super.onDestroy(); if (mService != null) { unbindService(mServiceConn); - } + } } </pre> @@ -185,13 +185,13 @@ querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList); </pre> <p>To retrieve this information from Google Play, call the {@code getSkuDetails} method on the In-app Billing Version 3 API, and pass the method the In-app Billing API version (“3”), the package name of your calling app, the purchase type (“inapp”), and the {@link android.os.Bundle} that you created.</p> <pre> -Bundle skuDetails = mService.getSkuDetails(3, +Bundle skuDetails = mService.getSkuDetails(3, getPackageName(), "inapp", querySkus); </pre> <p>If the request is successful, the returned {@link android.os.Bundle}has a response code of {@code BILLING_RESPONSE_RESULT_OK} (0).</p> <p class="note"><strong>Warning:</strong> Do not call the {@code getSkuDetails} method on the main thread. Calling this method triggers a network request which could block your main thread. Instead, create a separate thread and call the {@code getSkuDetails} method from inside that thread.</p> -<p>To see all the possible response codes from Google Play, see <a href="{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app Billing Reference</a>.</p> +<p>To see all the possible response codes from Google Play, see <a href="{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app Billing Reference</a>.</p> <p>The query results are stored in a String ArrayList with key {@code DETAILS_LIST}. The purchase information is stored in the String in JSON format. To see the types of product detail information that are returned, see <a href="{@docRoot}google/play/billing/billing_reference.html#getSkuDetails">In-app Billing Reference</a>.</p> @@ -201,7 +201,7 @@ int response = skuDetails.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); - + for (String thisResponse : responseList) { JSONObject object = new JSONObject(thisResponse); String sku = object.getString("productId"); @@ -232,12 +232,12 @@ startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); </pre> -<p>Google Play sends a response to your {@link android.app.PendingIntent} to the {@link android.app.Activity#onActivityResult onActivityResult} method of your application. The {@link android.app.Activity#onActivityResult onActivityResult} method will have a result code of {@code Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To see the types of order information that is returned in the response {@link android.content.Intent}, see <a href="{@docRoot}google/play/billing/billing_reference.html#getBuyIntent">In-app Billing Reference</a>.</p> +<p>Google Play sends a response to your {@link android.app.PendingIntent} to the {@link android.app.Activity#onActivityResult onActivityResult} method of your application. The {@link android.app.Activity#onActivityResult onActivityResult} method will have a result code of {@code Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To see the types of order information that is returned in the response {@link android.content.Intent}, see <a href="{@docRoot}google/play/billing/billing_reference.html#getBuyIntent">In-app Billing Reference</a>.</p> <p>The purchase data for the order is a String in JSON format that is mapped to the {@code INAPP_PURCHASE_DATA} key in the response {@link android.content.Intent}, for example: <pre> -'{ - "orderId":"12999763169054705758.1371079406387615", +'{ + "orderId":"12999763169054705758.1371079406387615", "packageName":"com.example.app", "productId":"exampleSku", "purchaseTime":1345678900000, @@ -259,17 +259,17 @@ return the entire token.</p> <p>Continuing from the previous example, you get the response code, purchase data, and signature from the response {@link android.content.Intent}.</p> <pre> @Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 1001) { +protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == 1001) { int responseCode = data.getIntExtra("RESPONSE_CODE", 0); String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - + if (resultCode == RESULT_OK) { try { JSONObject jo = new JSONObject(purchaseData); String sku = jo.getString("productId"); - alert("You have bought the " + sku + ". Excellent choice, + alert("You have bought the " + sku + ". Excellent choice, adventurer!"); } catch (JSONException e) { @@ -298,45 +298,45 @@ if (response == 0) { ArrayList<String> purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); ArrayList<String> signatureList = - ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE"); - String continuationToken = + ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); + String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN"); - + for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); String signature = signatureList.get(i); String sku = ownedSkus.get(i); - + // do something with this purchase information // e.g. display the updated list of products owned by user - } + } - // if continuationToken != null, call getPurchases again + // if continuationToken != null, call getPurchases again // and pass in the token to retrieve more items } </pre> <h3 id="Consume">Consuming a Purchase</h3> -<p>You can use the In-app Billing Version 3 API to track the ownership of -purchased in-app products in Google Play. Once an in-app product is purchased, -it is considered to be "owned" and cannot be purchased from Google Play. You -must send a consumption request for the in-app product before Google Play makes +<p>You can use the In-app Billing Version 3 API to track the ownership of +purchased in-app products in Google Play. Once an in-app product is purchased, +it is considered to be "owned" and cannot be purchased from Google Play. You +must send a consumption request for the in-app product before Google Play makes it available for purchase again.</p> -<p class="caution"><strong>Important</strong>: Managed in-app products are +<p class="caution"><strong>Important</strong>: Managed in-app products are consumable, but subscriptions are not.</p> -<p>How you use the consumption mechanism in your app is up to you. Typically, -you would implement consumption for in-app products with temporary benefits that -users may want to purchase multiple times (for example, in-game currency or -equipment). You would typically not want to implement consumption for in-app -products that are purchased once and provide a permanent effect (for example, +<p>How you use the consumption mechanism in your app is up to you. Typically, +you would implement consumption for in-app products with temporary benefits that +users may want to purchase multiple times (for example, in-game currency or +equipment). You would typically not want to implement consumption for in-app +products that are purchased once and provide a permanent effect (for example, a premium upgrade).</p> -<p>To record a purchase consumption, send the {@code consumePurchase} method to -the In-app Billing service and pass in the {@code purchaseToken} String value -that identifies the purchase to be removed. The {@code purchaseToken} is part -of the data returned in the {@code INAPP_PURCHASE_DATA} String by the Google -Play service following a successful purchase request. In this example, you are -recording the consumption of a product that is identified with the +<p>To record a purchase consumption, send the {@code consumePurchase} method to +the In-app Billing service and pass in the {@code purchaseToken} String value +that identifies the purchase to be removed. The {@code purchaseToken} is part +of the data returned in the {@code INAPP_PURCHASE_DATA} String by the Google +Play service following a successful purchase request. In this example, you are +recording the consumption of a product that is identified with the {@code purchaseToken} in the {@code token} variable.</p> <pre> int response = mService.consumePurchase(3, getPackageName(), token); @@ -346,10 +346,10 @@ int response = mService.consumePurchase(3, getPackageName(), token); <p class="note"><strong>Security Recommendation:</strong> You must send a consumption request before provisioning the benefit of the consumable in-app purchase to the user. Make sure that you have received a successful consumption response from Google Play before you provision the item.</p> <h3 id="Subs">Implementing Subscriptions</h3> -<p>Launching a purchase flow for a subscription is similar to launching the -purchase flow for a product, with the exception that the product type must be set -to "subs". The purchase result is delivered to your Activity's -{@link android.app.Activity#onActivityResult onActivityResult} method, exactly +<p>Launching a purchase flow for a subscription is similar to launching the +purchase flow for a product, with the exception that the product type must be set +to "subs". The purchase result is delivered to your Activity's +{@link android.app.Activity#onActivityResult onActivityResult} method, exactly as in the case of in-app products.</p> <pre> Bundle bundle = mService.getBuyIntent(3, "com.example.myapp", @@ -363,39 +363,39 @@ if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) { Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); } </pre> -<p>To query for active subscriptions, use the {@code getPurchases} method, again +<p>To query for active subscriptions, use the {@code getPurchases} method, again with the product type parameter set to "subs".</p> <pre> Bundle activeSubs = mService.getPurchases(3, "com.example.myapp", "subs", continueToken); </pre> -<p>The call returns a {@code Bundle} with all the active subscriptions owned by -the user. Once a subscription expires without renewal, it will no longer appear +<p>The call returns a {@code Bundle} with all the active subscriptions owned by +the user. Once a subscription expires without renewal, it will no longer appear in the returned {@code Bundle}.</p> <h2 id="billing-security">Securing Your Application</h2> -<p>To help ensure the integrity of the transaction information that is sent to -your application, Google Play signs the JSON string that contains the response -data for a purchase order. Google Play uses the private key that is associated -with your application in the Developer Console to create this signature. The +<p>To help ensure the integrity of the transaction information that is sent to +your application, Google Play signs the JSON string that contains the response +data for a purchase order. Google Play uses the private key that is associated +with your application in the Developer Console to create this signature. The Developer Console generates an RSA key pair for each application.<p> -<p class="note"><strong>Note:</strong>To find the public key portion of this key -pair, open your application's details in the Developer Console, then click on -<strong>Services & APIs</strong>, and look at the field titled +<p class="note"><strong>Note:</strong>To find the public key portion of this key +pair, open your application's details in the Developer Console, then click on +<strong>Services & APIs</strong>, and look at the field titled <strong>Your License Key for This Application</strong>.</p> -<p>The Base64-encoded RSA public key generated by Google Play is in binary -encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public +<p>The Base64-encoded RSA public key generated by Google Play is in binary +encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public key that is used with Google Play licensing.</p> -<p>When your application receives this signed response you can -use the public key portion of your RSA key pair to verify the signature. -By performing signature verification you can detect responses that have -been tampered with or that have been spoofed. You can perform this signature -verification step in your application; however, if your application connects -to a secure remote server then we recommend that you perform the signature +<p>When your application receives this signed response you can +use the public key portion of your RSA key pair to verify the signature. +By performing signature verification you can detect responses that have +been tampered with or that have been spoofed. You can perform this signature +verification step in your application; however, if your application connects +to a secure remote server then we recommend that you perform the signature verification on that server.</p> <p>For more information about best practices for security and design, see <a diff --git a/docs/html/guide/index.jd b/docs/html/guide/index.jd index d78a1b1..cb4f65c 100644 --- a/docs/html/guide/index.jd +++ b/docs/html/guide/index.jd @@ -30,7 +30,7 @@ screen for a user interface, and a <em>service</em> independently performs work in the background.</p> <p>From one component you can start another component using an <em>intent</em>. You can even start -a component in a different app, such an activity in a maps app to show an address. This model +a component in a different app, such as an activity in a maps app to show an address. This model provides multiple entry points for a single app and allows any app to behave as a user's "default" for an action that other apps may invoke.</p> diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index c1256f9..99e64d9 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -13,10 +13,11 @@ parent.link=manifest-intro.html android:<a href="#clear">clearTaskOnLaunch</a>=["true" | "false"] android:<a href="#config">configChanges</a>=["mcc", "mnc", "locale", "touchscreen", "keyboard", "keyboardHidden", - "navigation", "screenLayout", "fontScale", "uiMode", - "orientation", "screenSize", "smallestScreenSize"] - android:<a href="#dlmode">documentLaunchMode</a>=["intoExisting", "always", - "none", "never"] + "navigation", "screenLayout", "fontScale", + "uiMode", "orientation", "screenSize", + "smallestScreenSize"] + android:<a href="#dlmode">documentLaunchMode</a>=["intoExisting" | "always" | + "none" | "never"] android:<a href="#enabled">enabled</a>=["true" | "false"] android:<a href="#exclude">excludeFromRecents</a>=["true" | "false"] android:<a href="#exported">exported</a>=["true" | "false"] diff --git a/docs/html/images/tv/channel-info.png b/docs/html/images/tv/channel-info.png Binary files differnew file mode 100644 index 0000000..5a48078 --- /dev/null +++ b/docs/html/images/tv/channel-info.png diff --git a/docs/html/images/tv/do-not-attempt.png b/docs/html/images/tv/do-not-attempt.png Binary files differnew file mode 100644 index 0000000..18a8775 --- /dev/null +++ b/docs/html/images/tv/do-not-attempt.png diff --git a/docs/html/images/tv/prog-guide.png b/docs/html/images/tv/prog-guide.png Binary files differnew file mode 100644 index 0000000..caa2278 --- /dev/null +++ b/docs/html/images/tv/prog-guide.png diff --git a/docs/html/images/tv/tvinput-life.png b/docs/html/images/tv/tvinput-life.png Binary files differnew file mode 100644 index 0000000..fc53f89 --- /dev/null +++ b/docs/html/images/tv/tvinput-life.png diff --git a/docs/html/tools/testing-support-library/index.jd b/docs/html/tools/testing-support-library/index.jd index aeace8e..c8c9ef5 100644 --- a/docs/html/tools/testing-support-library/index.jd +++ b/docs/html/tools/testing-support-library/index.jd @@ -391,7 +391,9 @@ onView(withId(R.id.changeTextBt)).perform(click());</pre> <p> To learn more about using Espresso, see the - <a href="{@docRoot}reference/android/support/test/package-summary.html">API reference</a>. + <a href="{@docRoot}reference/android/support/test/package-summary.html">API reference</a> and + <a href="{@docRoot}training/testing/ui-testing/espresso-testing.html"> + Testing UI for a Single App</a> training. </p> <h3 id="UIAutomator"> @@ -531,7 +533,9 @@ allAppsButton.clickAndWaitForNewWindow();</pre> <p> To learn more about using UI Automator, see the - <a href="{@docRoot}reference/android/support/test/package-summary.html">API reference</a>. + <a href="{@docRoot}reference/android/support/test/package-summary.html">API reference</a> and + <a href="{@docRoot}training/testing/ui-testing/uiautomator-testing.html"> + Testing UI for Multiple Apps</a> training. </p> <h2 id="setup"> diff --git a/docs/html/training/auto/start/index.jd b/docs/html/training/auto/start/index.jd index 54500ac..22e7521 100644 --- a/docs/html/training/auto/start/index.jd +++ b/docs/html/training/auto/start/index.jd @@ -55,14 +55,6 @@ messaging services. </p> setting up your development environment and meeting the the minimum requirements to enable an app to communicate with Auto.</p> -<p class="note"><strong>Important:</strong> If you are planning to develop -apps for Auto, you are encouraged to begin enabling and testing your -apps now. However, Auto-enabled apps cannot be published at this time. -Join the -<a href="http://g.co/AndroidAutoDev" class="external-link">Auto -Developers Google+ community</a> for updates on when you will be able to submit -your Auto-enabled apps.</p> - <h2 id="dev-project">Set Up an Auto Project</h2> <p>This section describes how to create a new app or modify an existing app to communicate with Auto.</p> diff --git a/docs/html/training/custom-views/optimizing-view.jd b/docs/html/training/custom-views/optimizing-view.jd index 7f2e762..022618b 100644 --- a/docs/html/training/custom-views/optimizing-view.jd +++ b/docs/html/training/custom-views/optimizing-view.jd @@ -12,33 +12,21 @@ previous.link=making-interactive.html <div id="tb"> <h2>This lesson teaches you to</h2> - <ol> - <li><a href="#less">Do Less, Less Frequently</a></li> - <li><a href="#accelerate">Use Hardware Acceleration</a></li> - </ol> - - <h2>You should also read</h2> <ul> - <li><a href="{@docRoot}guide/topics/graphics/hardware-accel.html"> - Hardware Acceleration - </a> - </li> - </ul> -<h2>Try it out</h2> -<div class="download-box"> -<a href="{@docRoot}shareables/training/CustomView.zip" -class="button">Download the sample</a> -<p class="filename">CustomView.zip</p> -</div> -</div> + <li><a href="#less">Do Less, Less Frequently</a></li> + </ul> + <h2>Try it out</h2> + <div class="download-box"> + <a href="{@docRoot}shareables/training/CustomView.zip" + class="button">Download the sample</a> + <p class="filename">CustomView.zip</p> </div> - + </div> +</div> <p>Now that you have a well-designed view that responds to gestures and transitions between states, -you need to ensure -that the view runs fast. To avoid a UI that feels sluggish or stutters during playback, you must -ensure that your -animations consistently run at 60 frames per second.</p> +ensure that the view runs fast. To avoid a UI that feels sluggish or stutters during playback, +ensure that animations consistently run at 60 frames per second.</p> <h2 id="less">Do Less, Less Frequently</h2> @@ -52,19 +40,13 @@ would cause a stutter. Allocate objects during initialization, or between animat allocation while an animation is running.</p> -<p>In addition to making {@link android.view.View#onDraw onDraw()} leaner, you should also make sure +<p>In addition to making {@link android.view.View#onDraw onDraw()} leaner, also make sure it's called as infrequently as possible. Most calls to {@link android.view.View#onDraw onDraw()} are the result of a call to {@link android.view.View#invalidate() invalidate()}, so eliminate unnecessary calls to {@link android.view.View#invalidate() -invalidate()}. When possible, call the four-parameter variant of {@link -android.view.View#invalidate() invalidate()} -rather than the version that takes no parameters. The no-parameter variant invalidates the entire -view, while the -four-parameter variant invalidates only a specified portion of the view. This approach allows draw calls to -be more efficient and -can eliminate unnecessary invalidation of views that fall outside the invalid rectangle.</p> +invalidate()}.</p> <p>Another very expensive operation is traversing layouts. Any time a view calls {@link android.view.View#requestLayout() @@ -78,7 +60,7 @@ behave properly. These deep view hierarchies cause performance problems. Make yo as shallow as possible.</p> -<p>If you have a complex UI, you should consider writing a custom {@link android.view.ViewGroup +<p>If you have a complex UI, consider writing a custom {@link android.view.ViewGroup ViewGroup} to perform its layout. Unlike the built-in views, your custom view can make application-specific assumptions about the size and @@ -88,89 +70,3 @@ to extend {@link android.view.ViewGroup ViewGroup} as part of a custom view. Pie views, but it never measures them. Instead, it sets their sizes directly according to its own custom layout algorithm.</p> - -<h2 id="accelerate">Use Hardware Acceleration</h2> - -<p>As of Android 3.0, the Android 2D graphics system can be accelerated by the GPU (Graphics -Processing Unit) hardware -found in most newer Android devices. GPU hardware acceleration can result in a tremendous -performance increase for many -applications, but it isn't the right choice for every application. The Android framework -gives you the ability to finely control which parts of your application are or are not -hardware accelerated.</p> - -<p>See <a href="{@docRoot}guide/topics/graphics/hardware-accel.html">Hardware Acceleration</a> - in the Android Developers Guide for directions on how to enable acceleration at the - application, activity, or window level. Notice that in addition to the directions in - the developer guide, you must also set your application's target API to 11 or higher by - specifying {@code <uses-sdk - android:targetSdkVersion="11"/>} in your {@code AndroidManifest.xml} file.</p> - -<p>Once you've enabled hardware acceleration, you may or may not see a performance increase. -Mobile GPUs are very good at certain tasks, such as scaling, rotating, and translating -bitmapped images. They are not particularly good at other tasks, such as drawing lines or curves. To -get the most out of GPU acceleration, you should maximize the number of operations that the GPU is -good at, and minimize the number of operations that the GPU isn't good at.</p> - -<p>In the PieChart example, for instance, drawing the pie is relatively expensive. Redrawing the pie -each time it's -rotated causes the UI to feel sluggish. The solution is to place the pie chart into a child -{@link android.view.View} and set that -{@link android.view.View}'s -<a href="{@docRoot}reference/android/view/View.html#setLayerType(int, android.graphics.Paint)"> - layer type</a> to {@link android.view.View#LAYER_TYPE_HARDWARE}, so that the GPU can cache it as -a static -image. The sample -defines the child view as an inner class of {@code PieChart}, which minimizes the amount of code -changes that are needed -to implement this solution.</p> - -<pre> - private class PieView extends View { - - public PieView(Context context) { - super(context); - if (!isInEditMode()) { - setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - for (Item it : mData) { - mPiePaint.setShader(it.mShader); - canvas.drawArc(mBounds, - 360 - it.mEndAngle, - it.mEndAngle - it.mStartAngle, - true, mPiePaint); - } - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mBounds = new RectF(0, 0, w, h); - } - - RectF mBounds; - } -</pre> - -<p>After this code change, {@code PieChart.PieView.onDraw()} is called only when the view is first -shown. During the rest -of the application's lifetime, the pie chart is cached as an image, and redrawn at different -rotation angles by the GPU. -GPU hardware is particularly good at this sort of thing, and the performance difference is -immediately noticeable.</p> - -<p>There is a tradeoff, though. Caching images as hardware layers consumes video memory, which is a -limited resource. -For this reason, the final version of {@code PieChart.PieView} only sets its layer type to -{@link android.view.View#LAYER_TYPE_HARDWARE} -while the user is actively scrolling. At all other times, it sets its layer type to -{@link android.view.View#LAYER_TYPE_NONE}, which -allows the GPU to stop caching the image.</p> - -<p>Finally, don't forget to profile your code. Techniques that improve performance on one view -might negatively affect performance on another.</p> diff --git a/docs/html/training/testing/ui-testing/espresso-testing.jd b/docs/html/training/testing/ui-testing/espresso-testing.jd new file mode 100644 index 0000000..85f4ba4 --- /dev/null +++ b/docs/html/training/testing/ui-testing/espresso-testing.jd @@ -0,0 +1,579 @@ +page.title=Testing UI for a Single App +page.tags=testing,espresso +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + <h2>Dependencies and Prerequisites</h2> + + <ul> + <li>Android 2.2 (API level 8) or higher + </li> + + <li> + <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support + Library</a> + </li> + </ul> + + <h2> + This lesson teaches you to + </h2> + + <ol> + <li> + <a href="#setup">Set Up Espresso</a> + </li> + + <li> + <a href="#build">Create an Espresso Test Class</a> + </li> + + <li> + <a href="#run">Run Espresso Tests on a Device or Emulator</a> + </li> + </ol> + + <h2> + You should also read + </h2> + + <ul> + <li><a href="{@docRoot}reference/android/support/test/package-summary.html"> + Espresso API Reference</a></li> + </ul> + + <h2> + Try it out + </h2> + + <ul> + <li> + <a href="https://github.com/googlesamples/android-testing" + class="external-link">Espresso Code Samples</a> + </li> + </ul> + </div> + </div> + + <p> + Testing user interactions + within a single app helps to ensure that users do not + encounter unexpected results or have a poor experience when interacting with your app. + You should get into the habit of creating user interface (UI) tests if you need to verify + that the UI of your app is functioning correctly. + </p> + + <p> + The Espresso testing framework, provided by the + <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>, + provides APIs for writing UI tests to simulate user interactions within a + single target app. Espresso tests can run on devices running Android 2.2 (API level 8) and + higher. A key benefit of using Espresso is that it provides automatic synchronization of test + actions with the UI of the app you are testing. Espresso detects when the main thread is idle, + so it is able to run your test commands at the appropriate time, improving the reliability of + your tests. This capability also relieves you from having to adding any timing workarounds, + such as a sleep period, in your test code. + </p> + + <p> + The Espresso testing framework is an instrumentation-based API and works + with the + <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code + AndroidJUnitRunner}</a> test runner. + </p> + + <h2 id="setup"> + Set Up Espresso + </h2> + + <p> + Before you begin using Espresso, you must: + </p> + + <ul> + <li> + <strong>Install the Android Testing Support Library</strong>. The Espresso API is + located under the {@code com.android.support.test.espresso} package. These classes allow + you to create tests that use the Espresso testing framework. To learn how to install the + library, see <a href="{@docRoot}tools/testing-support-library/index.html#setup"> + Testing Support Library Setup</a>. + </li> + + <li> + <strong>Set up your project structure.</strong> In your Gradle project, the source code for + the target app that you want to test is typically placed under the {@code app/src/main} + folder. The source code for instrumentation tests, including + your Espresso tests, must be placed under the <code>app/src/androidTest</code> folder. To + learn more about setting up your project directory, see + <a href="{@docRoot}tools/projects/index.html">Managing Projects</a>. + </li> + + <li> + <strong>Specify your Android testing dependencies</strong>. In order for the + <a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plug-in for Gradle</a> to + correctly build and run your Espresso tests, you must specify the following libraries in + the {@code build.gradle} file of your Android app module: + + <pre> +dependencies { + androidTestCompile 'com.android.support.test:testing-support-lib:0.1' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0' +} +</pre> + </li> + + <li> + <strong>Turn off animations on your test device.</strong> Leaving system animations turned + on in the test device might cause unexpected results or may lead your test to fail. Turn + off animations from <em>Settings</em> by opening <em>Developing Options</em> and + turning all the following options off: + <ul> + <li> + <em>Window animation scale</em> + </li> + + <li> + <em>Transition animation scale</em> + </li> + + <li> + <em>Animator duration scale</em> + </li> + </ul> + </li> + </ul> + + <h2 id="build"> + Create an Espresso Test Class + </h2> + + <p> + To create an Espresso test, create a Java class or an + {@link android.test.ActivityInstrumentationTestCase2} + subclass that follows this programming model: + </p> + + <ol> + <li>Find the UI component you want to test in an {@link android.app.Activity} (for example, a + sign-in button in the app) by calling the + <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> method, or the + <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onData(org.hamcrest.Matcher<java.lang.Object>)"> + {@code onData()}</a> method for {@link android.widget.AdapterView} controls. + </li> + + <li>Simulate a specific user interaction to perform on that UI component, by calling the + <a href="{@docRoot}reference/android/support/test/espresso/ViewInteraction.html#perform(android.support.test.espresso.ViewAction...)">{@code ViewInteraction.perform()}</a> + or + <a href="{@docRoot}reference/android/support/test/espresso/DataInteraction.html#perform(android.support.test.espresso.ViewAction...)">{@code DataInteraction.perform()}</a> + method and passing in the user action (for example, click on the sign-in button). To sequence + multiple actions on the same UI component, chain them using a comma-separated list in your + method argument. + </li> + + <li>Repeat the steps above as necessary, to simulate a user flow across multiple + activities in the target app. + </li> + + <li>Use the + <a href="{@docRoot}reference/android/support/test/espresso/assertion/ViewAssertions.html">{@code ViewAssertions}</a> + methods to check that the UI reflects the expected + state or behavior, after these user interactions are performed. + </li> + </ol> + + <p> + These steps are covered in more detail in the sections below. + </p> + + <p> + The following code snippet shows how your test class might invoke this basic workflow: + </p> + +<pre> +onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher + .perform(click()) // click() is a ViewAction + .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion +</pre> + + <h3 id="espresso-aitc2"> + Using Espresso with ActivityInstrumentationTestCase2 + </h3> + + <p> + If you are subclassing {@link android.test.ActivityInstrumentationTestCase2} + to create your Espresso test class, you must inject an + {@link android.app.Instrumentation} instance into your test class. This step is required in + order for your Espresso test to run with the + <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code AndroidJUnitRunner}</a> + test runner. + </p> + + <p> + To do this, call the + {@link android.test.InstrumentationTestCase#injectInstrumentation(android.app.Instrumentation) injectInstrumentation()} + method and pass in the result of + <a href="{@docRoot}reference/android/support/test/InstrumentationRegistry.html#getInstrumentation()"> + {@code InstrumentationRegistry.getInstrumentation()}</a>, as shown in the following code + example: + </p> + +<pre> +import android.support.test.InstrumentationRegistry; + +public class MyEspressoTest + extends ActivityInstrumentationTestCase2<MyActivity> { + + private MyActivity mActivity; + + public MyEspressoTest() { + super(MyActivity.class); + } + + @Before + public void setUp() throws Exception { + super.setUp(); + injectInstrumentation(InstrumentationRegistry.getInstrumentation()); + mActivity = getActivity(); + } + + ... +} +</pre> + +<p class="note"><strong>Note:</strong> Previously, {@link android.test.InstrumentationTestRunner} +would inject the {@link android.app.Instrumentation} instance, but this test runner is being +deprecated.</p> + + <h3 id="accessing-ui-components"> + Accessing UI Components + </h3> + + <p> + Before Espresso can interact with the app under test, you must first specify the UI component + or <em>view</em>. Espresso supports the use of +<a href="http://hamcrest.org/" class="external-link">Hamcrest matchers</a> + for specifying views and adapters in your app. + </p> + + <p> + To find the view, call the <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> + method and pass in a view matcher that specifies the view that you are targeting. This is + described in more detail in <a href="#specifying-view-matcher">Specifying a View Matcher</a>. + The <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> method returns a + <a href="{@docRoot}reference/android/support/test/espresso/ViewInteraction.html"> + {@code ViewInteraction}</a> + object that allows your test to interact with the view. + However, calling the <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> method may not work if you want to locate a view in + an {@link android.widget.AdapterView} layout. In this case, follow the instructions in + <a href="#locating-adpeterview-view">Locating a view in an AdapterView</a> instead. + </p> + + <p class="note"> + <strong>Note</strong>: The <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> method does not check if the view you specified is + valid. Instead, Espresso searches only the current view hierarchy, using the matcher provided. + If no match is found, the method throws a + <a href="{@docRoot}reference/android/support/test/espresso/NoMatchingViewException.html"> + {@code NoMatchingViewException}</a>. + </p> + + <p> + The following code snippet shows how you might write a test that accesses an + {@link android.widget.EditText} field, enters a string of text, closes the virtual keyboard, + and then performs a button click. + </p> + +<pre> +public void testChangeText_sameActivity() { + // Type text and then press the button. + onView(withId(R.id.editTextUserInput)) + .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); + onView(withId(R.id.changeTextButton)).perform(click()); + + // Check that the text was changed. + ... +} +</pre> + + <h4 id="specifying-view-matcher"> + Specifying a View Matcher + </h4> + + <p> + You can specify a view matcher by using these approaches: + </p> + + <ul> + <li>Calling methods in the + <a href="{@docRoot}reference/android/support/test/espresso/matcher/ViewMatchers.html"> + {@code ViewMatchers}</a> class. For example, to find a view by looking for a text string it + displays, you can call a method like this: + <pre> +onView(withText("Sign-in")); +</pre> + +<p>Similarly you can call +<a href="{@docRoot}reference/android/support/test/espresso/matcher/ViewMatchers.html#withId(int)"> +{@code withId()}</a> and providing the resource ID ({@code R.id}) of the view, as shown in the +following example:</p> + +<pre> +onView(withId(R.id.button_signin)); +</pre> + + <p> + Android resource IDs are not guaranteed to be unique. If your test attempts to match to a + resource ID used by more than one view, Espresso throws an +<a href="{@docRoot}reference/android/support/test/espresso/AmbiguousViewMatcherException.html"> + {@code AmbiguousViewMatcherException}</a>. + </p> + </li> + <li>Using the Hamcrest + <a href="http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html" + class="external-link">{@code Matchers}</a> class. You can use the + {@code allOf()} methods to combine multiple matchers, such as + {@code containsString()} and {@code instanceOf()}. This approach allows you to + filter the match results more narrowly, as shown in the following example: +<pre> +onView(allOf(withId(R.id.button_signin), withText("Sign-in"))); +</pre> +<p>You can use the {@code not} keyword to filter for views that don't correspond to the matcher, as +shown in the following example:</p> +<pre> +onView(allOf(withId(R.id.button_signin), not(withText("Sign-out")))); +</pre> +<p>To use these methods in your test, import the {@code org.hamcrest.Matchers} package. To +learn more about Hamcrest matching, see the +<a href="http://hamcrest.org/" class="external-link">Hamcrest site</a>. +</p> + </li> + </ul> + + <p> + To improve the performance of your Espresso tests, specify the minimum matching information + needed to find your target view. For example, if a view is uniquely identifiable by its + descriptive text, you do not need to specify that it is also assignable from the + {@link android.widget.TextView} instance. + </p> + + <h4 id="#locating-adpeterview-view"> + Locating a view in an AdapterView + </h4> + + <p> + In an {@link android.widget.AdapterView} widget, the view is dynamically populated with child + views at runtime. If the target view you want to test is inside an + {@link android.widget.AdapterView} + (such as a {@link android.widget.ListView}, {@link android.widget.GridView}, or + {@link android.widget.Spinner}), the +<a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onView(org.hamcrest.Matcher<android.view.View>)"> + {@code onView()}</a> method might not work because only a + subset of the views may be loaded in the current view hierarchy. + </p> + + <p> + Instead, call the <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onData(org.hamcrest.Matcher<java.lang.Object>)">{@code onData()}</a> + method to obtain a + <a href="{@docRoot}reference/android/support/test/espresso/DataInteraction.html"> + {@code DataInteraction}</a> + object to access the target view element. Espresso handles loading the target view element + into the current view hierarchy. Espresso also takes care of scrolling to the target element, + and putting the element into focus. + </p> + + <p class="note"> + <strong>Note</strong>: The + <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onData(org.hamcrest.Matcher<java.lang.Object>)">{@code onData()}</a> + method does not check if if the item you specified corresponds with a view. Espresso searches + only the current view hierarchy. If no match is found, the method throws a + <a href="{@docRoot}reference/android/support/test/espresso/NoMatchingViewException.html"> + {@code NoMatchingViewException}</a>. + </p> + + <p> + The following code snippet shows how you can use the + <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onData(org.hamcrest.Matcher<java.lang.Object>)">{@code onData()}</a> + method together + with Hamcrest matching to search for a specific row in a list that contains a given string. + In this example, the {@code LongListActivity} class contains a list of strings exposed + through a {@link android.widget.SimpleAdapter}. + </p> + +<pre> +onData(allOf(is(instanceOf(Map.class)), + hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str)))); +</pre> + + <h3 id="perform-actions"> + Performing Actions + </h3> + + <p> + Call the <a href="{@docRoot}reference/android/support/test/espresso/ViewInteraction.html#perform(android.support.test.espresso.ViewAction...)">{@code ViewInteraction.perform()}</a> + or + <a href="{@docRoot}reference/android/support/test/espresso/DataInteraction.html#perform(android.support.test.espresso.ViewAction...)">{@code DataInteraction.perform()}</a> + methods to + simulate user interactions on the UI component. You must pass in one or more + <a href="{@docRoot}reference/android/support/test/espresso/ViewAction.html">{@code ViewAction}</a> + objects as arguments. Espresso fires each action in sequence according to + the given order, and executes them in the main thread. + </p> + + <p> + The + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html">{@code ViewActions}</a> + class provides a list of helper methods for specifying common actions. + You can use these methods as convenient shortcuts instead of creating and configuring + individual <a href="{@docRoot}reference/android/support/test/espresso/ViewAction.html">{@code ViewAction}</a> + objects. You can specify such actions as: + </p> + + <ul> + <li> + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#click()">{@code ViewActions.click()}</a>: + Clicks on the view. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#typeText(java.lang.String)">{@code ViewActions.typeText()}</a>: + Clicks on a view and enters a specified string. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#scrollTo()">{@code ViewActions.scrollTo()}</a>: + Scrolls to the view. The + target view must be subclassed from {@link android.widget.ScrollView} + and the value of its + <a href="http://developer.android.com/reference/android/view/View.html#attr_android:visibility">{@code android:visibility}</a> + property must be {@link android.view.View#VISIBLE}. For views that extend + {@link android.widget.AdapterView} (for example, + {@link android.widget.ListView}), + the + <a href="{@docRoot}reference/android/support/test/espresso/Espresso.html#onData(org.hamcrest.Matcher<java.lang.Object>)">{@code onData()}</a> + method takes care of scrolling for you. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#pressKey(int)">{@code ViewActions.pressKey()}</a>: + Performs a key press using a specified keycode. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#clearText()">{@code ViewActions.clearText()}</a>: + Clears the text in the target view. + </li> + </ul> + + <p> + If the target view is inside a {@link android.widget.ScrollView}, perform the + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#scrollTo()">{@code ViewActions.scrollTo()}</a> + action first to display the view in the screen before other proceeding + with other actions. The + <a href="{@docRoot}reference/android/support/test/espresso/action/ViewActions.html#scrollTo()">{@code ViewActions.scrollTo()}</a> + action will have no effect if the view is already displayed. + </p> + + <h3 id="verify-results"> + Verifying Results + </h3> + + <p> + Call the + <a href="{@docRoot}reference/android/support/test/espresso/ViewInteraction.html#check(android.support.test.espresso.ViewAssertion)">{@code ViewInteraction.check()}</a> + or + <a href="{@docRoot}reference/android/support/test/espresso/DataInteraction.html#check(android.support.test.espresso.ViewAssertion)">{@code DataInteraction.check()}</a> + method to assert + that the view in the UI matches some expected state. You must pass in a + <a href="{@docRoot}reference/android/support/test/espresso/ViewAssertion.html"> + {@code ViewAssertion}</a> object as the argument. If the assertion fails, Espresso throws + an {@link junit.framework.AssertionFailedError}. + </p> + + <p> + The + <a href="{@docRoot}reference/android/support/test/espresso/assertion/ViewAssertions.html">{@code ViewAssertions}</a> + class provides a list of helper methods for specifying common + assertions. The assertions you can use include: + </p> + + <ul> + <li> + <a href="{@docRoot}reference/android/support/test/espresso/assertion/ViewAssertions.html#doesNotExist()">{@code doesNotExist}</a>: +Asserts that there is no view matching the specified criteria in the current view hierarchy. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/assertion/ViewAssertions.html#matches(org.hamcrest.Matcher<? super android.view.View>)">{@code matches}</a>: + Asserts that the specified view exists in the current view hierarchy + and its state matches some given Hamcrest matcher. + </li> + + <li> + <a href="{@docRoot}reference/android/support/test/espresso/assertion/ViewAssertions.html#selectedDescendantsMatch(org.hamcrest.Matcher<android.view.View>, org.hamcrest.Matcher<android.view.View>)">{@code selectedDescendentsMatch}</a> + : Asserts that the specified children views for a + parent view exist, and their state matches some given Hamcrest matcher. + </li> + </ul> + + <p> + The following code snippet shows how you might check that the text displayed in the UI has + the same value as the text previously entered in the + {@link android.widget.EditText} field. + </p> +<pre> +public void testChangeText_sameActivity() { + // Type text and then press the button. + ... + + // Check that the text was changed. + onView(withId(R.id.textToBeChanged)) + .check(matches(withText(STRING_TO_BE_TYPED))); +} +</pre> + +<h2 id="run">Run Espresso Tests on a Device or Emulator</h2> + + <p> + To run Espresso tests, you must use the + <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code AndroidJUnitRunner}</a> + class provided in the + <a href="{@docRoot}tools/testing-support-library/index.html"> + Android Testing Support Library</a> as your default test runner. The + <a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plug-in for + Gradle</a> provides a default directory ({@code src/androidTest/java}) for you to store the + instrumented test classes and test suites that you want to run on a device. The + plug-in compiles the test code in that directory and then executes the test app using + the configured test runner class. + </p> + + <p> + To run Espresso tests in your Gradle project: + </p> + + <ol> + <li>Specify + <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code AndroidJUnitRunner}</a> + as the default test instrumentation runner in + your {@code build.gradle} file: + + <pre> +android { + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } +}</pre> + </li> + <li>Run your tests from the command-line by calling the the {@code connectedCheck} + (or {@code cC}) task: + <pre> +./gradlew cC</pre> + </li> + </ol>
\ No newline at end of file diff --git a/docs/html/training/testing/ui-testing/index.jd b/docs/html/training/testing/ui-testing/index.jd new file mode 100644 index 0000000..20422f7 --- /dev/null +++ b/docs/html/training/testing/ui-testing/index.jd @@ -0,0 +1,76 @@ +page.title=Automating User Interface Tests +page.tags=testing + +trainingnavtop=true +startpage=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2> + You should also read + </h2> + + <ul> + <li> + <a href="{@docRoot}tools/testing-support-library/index.html">Testing Support Library</a> + </li> + </ul> +</div> +</div> + +<p>User interface (UI) testing lets you ensure that your app meets its functional requirements +and achieves a high standard of quality such that it is more likely to be successfully adopted by +users.</p> + +<p>One approach to UI testing is to simply have a human tester perform a set of user operations on +the target app and verify that it is behaving correctly. However, this manual approach can be +time-consuming, tedious, and error-prone. A more efficient approach is to write your UI +tests such that user actions are performed in an automated way. The automated approach allows +you to run your tests quickly and reliably in a repeatable manner.</p> + +<p class="note"><strong>Note: </strong>It is strongly encouraged that you use +<a href="{@docRoot}sdk/installing/studio.html">Android Studio</a> for +building your test apps, because it provides project setup, library inclusion, and packaging +conveniences. This class assumes you are using Android Studio.</p> + +<p>To automate UI tests with Android Studio, you implement your test code in a separate +Android test folder ({@code src/androidTest/java}). The +<a href="{@docRoot}tools/building/plugin-for-gradle.html">Android +Plug-in for Gradle</a> builds a test app based on your test code, then loads the test app on the +same device as the target app. In your test code, you can use UI testing frameworks to +simulate user interactions on the target app, in order to perform testing tasks that cover specific +usage scenarios.</p> + +<p>For testing Android apps, you typically create these types of automated UI tests:</p> + +<ul> +<li><em>UI tests that span a single app:</em> This type of test verifies that the target app behaves +as expected when a user performs a specific action or enters a specific input in its activities. +It allows you to check that the target app returns the correct UI output in response +to user interactions in the app’s activities. UI testing frameworks like Espresso allow you to +programmatically simulate user actions and test complex intra-app user interactions.</li> +<li><em>UI tests that span multiple apps:</em> This type of test verifies the correct behavior of +interactions between different user apps or between user apps and system apps. For example, you +might want to test that your camera app shares images correctly with a 3rd-party social media app, +or with the default Android Photos app. UI testing frameworks that support cross-app interactions, +such as UI Automator, allow you to create tests for such scenarios.</li> +</ul> + +<p>The lessons in this class teach you how to use the tools and APIs in the +<a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a> +to build these types of automated tests. Before you begin building tests using these +APIs, you must install the Android Testing Support Library, as described in +<a href="{@docRoot}tools/testing-support-library/index.html#setup">Downloading the Android +Testing Support Library</a>.</p> + +<h2>Lessons</h2> +<dl> + <dt><strong><a href="espresso-testing.html"> +Testing UI for a Single App</a></strong></dt> + <dd>Learn how to test UI in a single app by using the Espresso testing framework.</dd> + <dt><strong><a href="uiautomator-testing.html"> +Testing UI for Multiple Apps</a></strong></dt> + <dd>Learn how to test UI in multiple apps by using the UI Automator testing framework</dd> +</dl>
\ No newline at end of file diff --git a/docs/html/training/testing/ui-testing/uiautomator-testing.jd b/docs/html/training/testing/ui-testing/uiautomator-testing.jd new file mode 100644 index 0000000..e314b70 --- /dev/null +++ b/docs/html/training/testing/ui-testing/uiautomator-testing.jd @@ -0,0 +1,520 @@ +page.title=Testing UI for Multiple Apps +page.tags=testing,ui automator +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + <h2>Dependencies and Prerequisites</h2> + + <ul> + <li>Android 4.3 (API level 18) or higher</li> + <li><a href="{@docRoot}tools/testing-support-library/index.html"> + Android Testing Support Library</a></li> + </ul> + + <h2>This lesson teaches you to</h2> + + <ol> + <li><a href="#setup">Set Up UI Automator</a></li> + <li><a href="#build">Create a UI Automator Test Class</a></li> + <li><a href="#run">Run UI Automator Tests on a Device or Emulator</a></li> + </ol> + + <h2>You should also read</h2> + + <ul> + <li><a href="{@docRoot}reference/android/support/test/package-summary.html"> +UI Automator API Reference</a></li> + </ul> + + <h2>Try it out</h2> + + <ul> + <li><a href="https://github.com/googlesamples/android-testing" +class="external-link">UI Automator Code Samples</a></li> + </ul> +</div> +</div> + +<p>A user interface (UI) test that involves user interactions across multiple apps lets you +verify that your app behaves correctly when the user flow crosses into other apps or into the +system UI. An example of such a user flow is a messaging app that lets the user enter a text +message, launches the Android contact picker so that the users can select recipients to send the +message to, and then returns control to the original app for the user to submit the message.</p> + +<p>This lesson covers how to write such UI tests using the +UI Automator testing framework provided by the +<a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>. +The UI Automator APIs let you interact with visible elements on a device, regardless of +which {@link android.app.Activity} is in focus. Your test can look up a UI component by using +convenient descriptors such as the text displayed in that component or its content description. UI +Automator tests can run on devices running Android 4.3 (API level 18) or higher.</p> + +<p>The UI Automator testing framework is an instrumentation-based API and works +with the +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html"> + {@code AndroidJUnitRunner}</a> +test runner. +</p> + +<h2 id="setup">Set Up UI Automator</h2> +<p>Before you begin using UI Automator, you must:</p> + + <ul> + <li> + <strong>Install the Android Testing Support Library</strong>. The UI Automator API is + located under the {@code com.android.support.test.uiautomator} package. These classes allow + you to create tests that use the Espresso testing framework. To learn how to install the + library, see <a href="{@docRoot}tools/testing-support-library/index.html#setup"> + Testing Support Library Setup</a>. + </li> + + <li> + <strong>Set up your project structure.</strong> In your Gradle project, the source code for + the target app that you want to test is typically placed under the {@code app/src/main} + folder. The source code for instrumentation tests, including + your UI Automator tests, must be placed under the <code>app/src/androidTest</code> folder. + To learn more about setting up your project directory, see + <a href="{@docRoot}tools/projects/index.html">Managing Projects</a>. + </li> + + <li> + <strong>Specify your Android testing dependencies</strong>. In order for the + <a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plug-in for Gradle</a> to + correctly build and run your UI Automator tests, you must specify the following libraries in + the {@code build.gradle} file of your Android app module: + + <pre> +dependencies { + androidTestCompile 'com.android.support.test:testing-support-lib:0.1' + androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.0.0' +} +</pre> + </li> + </ul> + +<p>To optimize your UI Automator testing, you should first inspect the target app’s UI components +and ensure that they are accessible. These optimization tips are described in the next two +sections.</p> + +<h3 id="inspecting-ui">Inspecting the UI on a device</h3> +<p>Before designing your test, inspect the UI components that are visible on the device. To +ensure that your UI Automator tests can access these components, check that these components +have visible text labels, +<a href="http://developer.android.com/reference/android/view/View.html#attr_android:contentDescription"> +{@code android:contentDescription}</a> +values, or both.</p> + +<p>The {@code uiautomatorviewer} tool provides a convenient visual interface to inspect the layout +hierarchy and view the properties of UI components that are visible on the foreground of the device. +This information lets you create more fine-grained tests using UI Automator. For example, you can +create a UI selector that matches a specific visible property. </p> + +<p>To launch the {@code uiautomatorviewer} tool:</p> + +<ol> + <li>Launch the target app on a physical device.</li> + <li>Connect the device to your development machine.</li> + <li>Open a terminal window and navigate to the {@code <android-sdk>/tools/} directory.</li> + <li>Run the tool with this command: +<pre>$ uiautomatorviewer</pre> + </li> +</ol> + +<p>To view the UI properties for your application:</p> + +<ol> + <li>In the {@code uiautomatorviewer} interface, click the <strong>Device Screenshot</strong> +button.</li> + <li>Hover over the snapshot in the left-hand panel to see the UI components identified by the +{@code uiautomatorviewertool}. The properties are listed in the lower right-hand panel and the +layout hierarchy in the upper right-hand panel.</li> + <li>Optionally, click on the <strong>Toggle NAF Nodes</strong> button to see UI components that +are non-accessible to UI Automator. Only limited information may be available for these +components.</li> +</ol> + +<p>To learn about the common types of UI components provided by Android, see +<a href="{@docRoot}guide/topics/ui/index.html">User Interface</a>.</p> + +<h3>Ensuring your Activity is accessible</h3> +<p>The UI Automator test framework depends on the accessibility features of the Android framework +to look up individual UI elements. As a developer, you should implement these minimum +optimizations in your {@link android.app.Activity} to support UI Automator:</p> + +<ul> +<li>Use the +<a href="{@docRoot}reference/android/view/View.html#attr_android:contentDescription"> + {@code android:contentDescription}</a> +attribute to label the {@link android.widget.ImageButton}, {@link android.widget.ImageView}, +{@link android.widget.CheckBox} and other user interface controls.</li> +<li>Provide an <a href="{@docRoot}reference/android/widget/TextView.html#attr_android:hint">{@code android:hint}</a> +attribute instead of a content description for {@link android.widget.EditText} fields.</li> +<li>Associate an <a href="http://developer.android.com/reference/android/widget/TextView.html#attr_android:hint"> + {@code android:hint}</a> +attribute with any graphical icons used by controls that provide feedback to the user +(for example, status or state information).</li> +<li>Use the {@code uiautomatorviewer} tool to ensure that the UI component is accessible to the +testing framework. You can also test the application by turning on accessibility services like +TalkBack and Explore by Touch, and try using your application using only directional controls.</li> +</ul> + +<p>Generally, app developers get accessibility support for free, courtesy of +the {@link android.view.View} and {@link android.view.ViewGroup} +classes. However, some apps use custom view elements to provide a richer user experience. Such +custom elements won't get the accessibility support that is provided by the standard Android UI +elements. If this applies to your app, make sure that it exposes the custom-drawn UI element to +Android accessibility services by implementing the +{@link android.view.accessibility.AccessibilityNodeProvider} class.</p> + +<p>If the custom view element contains a single element, make it accessible by +<a href="{@docRoot}guide/topics/ui/accessibility/apps.html#accessibility-methods">implementing +accessibility API methods</a>. +If the custom view contains elements that are not views themselves (for example, a +{@link android.webkit.WebView}, make sure it implements the +{@link android.view.accessibility.AccessibilityNodeProvider} class. For container views that +extend an existing container implementation +(for example, a {@link android.widget.ListView}), implementing +{@link android.view.accessibility.AccessibilityNodeProvider} is not necessary.</p> + +<p>For more information about implementing and testing accessibility, see +<a href="{@docRoot}guide/topics/ui/accessibility/apps.html">Making Applications Accessible</a>.</p> + +<h2 id="build">Create a UI Automator Test Class</h2> + +<p>To build a UI Automator test, create a class that extends +{@link android.test.InstrumentationTestCase}. Implement the following programming model in your +UI Automator test class:</p> + +<ol> +<li>Get a + <a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html">{@code UiDevice}</a> + object to access the device you want to test, by calling the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html#getInstance(android.app.Instrumentation)"> +{@code getInstance()}</a> +method and passing it an {@link android.app.Instrumentation} object as the argument.</li> +<li>Get a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +object to access a UI component that is displayed on the device + (for example, the current view in the foreground), by calling the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html#findObject(android.support.test.uiautomator.UiSelector)"> + {@code findObject()}</a> +method. +</li> +<li>Simulate a specific user interaction to perform on that UI component, by calling a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +method; for example, call +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#performMultiPointerGesture(android.view.MotionEvent.PointerCoords[]...)"> + {@code performMultiPointerGesture()}</a> +to simulate a multi-touch gesture, and +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#setText(java.lang.String)">{@code setText()}</a> +to edit a text field. You can call on the APIs in steps 2 and 3 repeatedly as necessary to test +more complex user interactions that involve multiple UI components or sequences of user actions.</li> +<li>Check that the UI reflects the expected state or behavior, after these user interactions are + performed. </li> +</ol> + +<p>These steps are covered in more detail in the sections below.</p> + +<h3 id="accessing-ui-components">Accessing UI Components</h3> +<p>The +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html">{@code UiDevice}</a> + object is the primary way you access and manipulate the state of the +device. In your tests, you can call +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html">{@code UiDevice}</a> +methods to check for the state of various properties, such as current orientation or display size. +Your test can use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html">{@code UiDevice}</a> +object to perform device-level actions, such as forcing the device into a specific rotation, +pressing D-pad hardware buttons, and pressing the Home and Menu buttons.</p> + +<p>It’s good practice to start your test from the Home screen of the device. From the Home screen +(or some other starting location you’ve chosen in the device), you can call the methods provided by +the UI Automator API to select and interact with specific UI elements. </p> + +<p>The following code snippet shows how your test might get an instance of +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html">{@code UiDevice}</a> +and simulate a Home button press:</p> + +<pre> +import android.test.InstrumentationTestCase; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.By; + +public class CalculatorUiTest extends InstrumentationTestCase { + + private UiDevice mDevice; + + public void setUp() { + // Initialize UiDevice instance + mDevice = UiDevice.getInstance(getInstrumentation()); + + // Start from the home screen + mDevice.pressHome(); + mDevice.wait(Until.hasObject(By.pkg(getHomeScreenPackage()).depth(0)), + } +} +</pre> + +<p>Use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiDevice.html#findObject(android.support.test.uiautomator.UiSelector)">{@code findObject()}</a> +method to retrieve a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +which represents a view that matches a given selector criteria. You can reuse the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +instances that you have created in other parts of your app testing, as needed. Note that the +UI Automator test framework searches the current display for a match every time your test uses a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +instance to click on a UI element or query a property.</p> + +<p>The following snippet shows how your test might construct +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +instances that represent a Cancel button and a OK button in an app.</p> + +<pre> +UiObject cancelButton = mDevice.findObject(new UiSelector() + .text("Cancel")) + .className("android.widget.Button")); +UiObject okButton = mDevice.findObject(new UiSelector() + .text("OK")) + .className("android.widget.Button")); + +// Simulate a user-click on the OK button, if found. +if(okButton.exists() && okButton.isEnabled()) { + okButton.click(); +} +</pre> + +<h4 id="specifying-selector">Specifying a selector</h4> +<p>If you want to access a specific UI component in an app, use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiSelector.html">{@code UiSelector}</a> +class. This class represents a query for specific elements in the +currently displayed UI. </p> + +<p>If more than one matching element is found, the first matching element in the layout hierarchy +is returned as the target +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a>. +When constructing a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiSelector.html">{@code UiSelector}</a>, +you can chain together multiple properties to refine your search. If no matching UI element is +found, a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObjectNotFoundException.html"> +{@code UiAutomatorObjectNotFoundException}</a> is thrown. </p> + +<p>You can use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiSelector.html#childSelector(android.support.test.uiautomator.UiSelector)">{@code childSelector()}</a> +method to nest multiple +<a href="{@docRoot}reference/android/support/test/uiautomator/UiSelector.html">{@code UiSelector}</a> +instances. For example, the following code example shows how your test might specify a search to +find the first {@link android.widget.ListView} in the currently displayed UI, then search within that +{@link android.widget.ListView} to find a UI element with the text property Apps.</p> + +<pre> +UiObject appItem = new UiObject(new UiSelector() + .className("android.widget.ListView") + .instance(1) + .childSelector(new UiSelector() + .text("Apps"))); +</pre> + +<p>As a best practice, when specifying a selector, you should use a Resource ID (if one is assigned +to a UI element) instead of a text element or content-descriptor. Not all elements have a text +element (for example, icons in a toolbar). Text selectors are brittle and can lead to test failures +if there are minor changes to the UI. They may also not scale across different languages; your text +selectors may not match translated strings.</p> + +<p>It can be useful to specify the object state in your selector criteria. For example, if you want +to select a list of all checked elements so that you can uncheck them, call the +<a href="{@docRoot}reference/android/support/test/uiautomator/By.html#checked(boolean)"> +{@code checked()}</a> +method with the argument set to {@code true}.</p> + +<h3 id="performing-actions">Performing Actions</h3> + +<p>Once your test has obtained a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +object, you can call the methods in the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a> +class to perform user interactions on the UI component represented by that +object. You can specify such actions as:</p> + +<ul> +<li> +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#click()"> + {@code click()}</a> +: Clicks the center of the visible bounds of the UI element.</li> +<li> +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#dragTo(int, int, int)"> + {@code dragTo()}</a> +: Drags this object to arbitrary coordinates.</li> +<li> +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#setText(java.lang.String)"> + {@code setText()}</a> +: Sets the text in an editable field, after clearing the field's content. +Conversely, the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#clearTextField()"> + {@code clearTextField()}</a> +method clears the existing text in an editable field.</li> +<li> +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#swipeUp(int)"> + {@code swipeUp()}</a> +: Performs the swipe up action on the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html">{@code UiObject}</a>. +Similarly, the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#swipeDown(int)"> + {@code swipeDown()}</a>, +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#swipeLeft(int)"> + {@code swipeLeft()}</a>, and +<a href="{@docRoot}reference/android/support/test/uiautomator/UiObject.html#swipeRight(int)"> + {@code swipeRight()}</a> +methods perform corresponding actions.</li> +</ul> + +<p>The UI Automator testing framework allows you to send an +{@link android.content.Intent} +or launch an {@link android.app.Activity} +without using shell commands, by getting a +{@link android.content.Context} +object through +{@link android.app.Instrumentation#getContext() getContext()}.</p> + +<p>The following snippet shows how your test can use an +{@link android.content.Intent} to launch the app under test. This approach is useful when you are +only interested in testing the calculator app, and don't care about the launcher.</p> + +<pre> +public void setUp() { + ... + + // Launch a simple calculator app + Context context = getInstrumentation().getContext(); + Intent intent = context.getPackageManager() + .getLaunchIntentForPackage(CALC_PACKAGE); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + // Clear out any previous instances + context.startActivity(intent); + mDevice.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT); +} +</pre> + +<h4 id="actions-on-collections">Performing actions on collections</h4> + +<p>Use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiCollection.html"> + {@code UiCollection}</a> +class if you want to simulate user interactions on a +collection of items (for example, songs in a music album or a list of emails in an Inbox). To +create a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiCollection.html"> + {@code UiCollection}</a> +object, specify a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiSelector.html">{@code UiSelector}</a> +that searches for a +UI container or a wrapper of other child UI elements, such as a layout view that contains child UI +elements.</p> + +<p>The following code snippet shows how your test might construct a +<a href="{@docRoot}reference/android/support/test/uiautomator/UiCollection.html"> + {@code UiCollection}</a> +to represent a video album that is displayed within a {@link android.widget.FrameLayout}:</p> + +<pre> +UiCollection videos = new UiCollection(new UiSelector() + .className("android.widget.FrameLayout")); + +// Retrieve the number of videos in this collection: +int count = videos.getChildCount(new UiSelector() + .className("android.widget.LinearLayout")); + +// Find a specific video and simulate a user-click on it +UiObject video = videos.getChildByText(new UiSelector() + .className("android.widget.LinearLayout"), "Cute Baby Laughing"); +video.click(); + +// Simulate selecting a checkbox that is associated with the video +UiObject checkBox = video.getChild(new UiSelector() + .className("android.widget.Checkbox")); +if(!checkBox.isSelected()) checkbox.click(); +</pre> + +<h4 id="actions-on-scrollable-views">Performing actions on scrollable views</h4> +<p>Use the +<a href="{@docRoot}reference/android/support/test/uiautomator/UiScrollable.html"> + {@code UiScrollable}</a> +class to simulate vertical or horizontal scrolling across a display. This technique is helpful when +a UI element is positioned off-screen and you need to scroll to bring it into view.</p> + +<p>The following code snippet shows how to simulate scrolling down the Settings menu and clicking +on an About tablet option:</p> + +<pre> +UiScrollable settingsItem = new UiScrollable(new UiSelector() + .className("android.widget.ListView")); +UiObject about = settingsItem.getChildByText(new UiSelector() + .className("android.widget.LinearLayout"), "About tablet"); +about.click(); +</pre> + +<h3 id="verifying-results">Verifying Results</h3> +<p>The {@link android.test.InstrumentationTestCase} extends {@link junit.framework.TestCase}, so +you can use standard JUnit <a href="http://junit.org/javadoc/latest/org/junit/Assert.html" +class="external-link">{@code Assert}</a> methods to test +that UI components in the app return the expected results. </p> + +<p>The following snippet shows how your test can locate several buttons in a calculator app, click +on them in order, then verify that the correct result is displayed.</p> + +<pre> +private static final String CALC_PACKAGE = "com.myexample.calc"; + +public void testTwoPlusThreeEqualsFive() { + // Enter an equation: 2 + 3 = ? + mDevice.findObject(new UiSelector() + .packageName(CALC_PACKAGE).resourceId("two")).click(); + mDevice.findObject(new UiSelector() + .packageName(CALC_PACKAGE).resourceId("plus")).click(); + mDevice.findObject(new UiSelector() + .packageName(CALC_PACKAGE).resourceId("three")).click(); + mDevice.findObject(new UiSelector() + .packageName(CALC_PACKAGE).resourceId("equals")).click(); + + // Verify the result = 5 + UiObject result = mDevice.findObject(By.res(CALC_PACKAGE, "result")); + assertEquals("5", result.getText()); +} +</pre> + +<h2 id="run">Run UI Automator Tests on a Device or Emulator</h2> +<p>UI Automator tests are based on the {@link android.app.Instrumentation} class. The +<a href="https://developer.android.com/tools/building/plugin-for-gradle.html"> + Android Plug-in for Gradle</a> +provides a default directory ({@code src/androidTest/java}) for you to store the instrumented test +classes and test suites that you want to run on a device. The plug-in compiles the test +code in that directory and then executes the test app using a test runner class. You are +strongly encouraged to use the +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code AndroidJUnitRunner}</a> +class provided in the +<a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a> +as your default test runner. </p> + +<p>To run UI Automator tests in your Gradle project:</p> + +<ol> +<li>Specify +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html">{@code AndroidJUnitRunner}</a> +as the default test instrumentation runner in your {@code build.gradle} file: +<pre> +android { + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } +}</pre> +</li> +<li>Run your tests from the command-line by calling the {@code connectedCheck} + (or {@code cC}) task: +<pre>./gradlew cC</pre> +</li> +</ol>
\ No newline at end of file diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 11ae1a6..3ee7ab7 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -998,10 +998,25 @@ include the action bar on devices running Android 2.1 or higher." Building TV Games</a> </li> - <li> - <a href="<?cs var:toroot ?>training/tv/tif/index.html" + <li class="nav-section"> + <div class="nav-section-header"> + <a href="<?cs var:toroot ?>training/tv/tif/index.html" description="How to build Live TV apps."> Building Live TV Apps</a> + </div> + <ul> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/tvinput.html"> + Developing a TV Input Service</a> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/channel.html"> + Working with Channel Data</a> + </li> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/ui.html"> + Managing User Interaction</a> + </li> + </ul> </li> <li> @@ -1825,6 +1840,24 @@ results." </ul> </li> </ul> + <ul> + <li class="nav-section"> + <div class="nav-section-header"><a href="<?cs var:toroot ?>training/testing/ui-testing/index.html" + description="How to automate your user interface tests for Android apps."> + Automating UI Tests + </a></div> + <ul> + <li><a href="<?cs var:toroot ?>training/testing/ui-testing/espresso-testing.html"> + <span class="en">Testing UI for a Single App</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/testing/ui-testing/uiautomator-testing.html"> + <span class="en">Testing UI for Multiple Apps</span> + </a> + </li> + </ul> + </li> + </ul> </li> <!-- End best Testing --> diff --git a/docs/html/training/tv/tif/channel.jd b/docs/html/training/tv/tif/channel.jd new file mode 100644 index 0000000..999f1ca --- /dev/null +++ b/docs/html/training/tv/tif/channel.jd @@ -0,0 +1,239 @@ +page.title=Working with Channel Data +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#permission">Get Permission</a></li> + <li><a href="#register">Register Channels in the Database</a></li> + <li><a href="#update">Update Channel Data</a></li> + </ol> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>Your TV input must provide Electronic Program Guide (EPG) data for at least one channel in its +setup activity. You should also periodically update that data, with consideration for the size of +the update and the processing thread that handles it. This lesson discusses creating and updating +channel and program data on the system database with these considerations in mind.</p> + +<p> </p> + +<h2 id="permission">Get Permission</h2> + +<p>In order for your TV input to work with EPG data, it must declare the +read and write permissions in its Android manifest file as follows:</p> + +<pre> +<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" /> +<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> +</pre> + +<h2 id="register">Register Channels in the Database</h2> + +<p>The Android TV system database maintains records of channel data for TV inputs. In your setup +activity, for each of your channels, you must map your channel data to the following fields of the +{@link android.media.tv.TvContract.Channels} class:</p> + +<ul> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NAME} - the displayed name of the + channel</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER} - the displayed channel + number</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_INPUT_ID} - the ID of the TV input service</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_TYPE} - the channel's service type</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_TYPE} - the channel's broadcast standard + type</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_VIDEO_FORMAT} - the default video format + for the channel</li> +</ul> + +<p>Although the TV input framework is generic enough to handle both traditional broadcast and +over-the-top (OTT) content without any distinction, you may want to define the following columns in +addition to those above to better identify traditional broadcast channels:</p> + +<ul> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_ORIGINAL_NETWORK_ID} - the television + network ID</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_ID} - the service ID</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_TRANSPORT_STREAM_ID} - the transport stream + ID</li> +</ul> + +<p>For internet streaming based TV inputs, assign your own values to the above accordingly so that +each channel can be identified uniquely.</p> + +<p>Pull your channel metadata (in XML, JSON, or whatever) from your backend server, and in your setup +activity map the values to the system database as follows:</p> + +<pre> +ContentValues values = new ContentValues(); + +values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.mNumber); +values.put(Channels.COLUMN_DISPLAY_NAME, channel.mName); +values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.mOriginalNetworkId); +values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.mTransportStreamId); +values.put(Channels.COLUMN_SERVICE_ID, channel.mServiceId); +values.put(Channels.COLUMN_VIDEO_FORMAT, channel.mVideoFormat); + +Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values); +</pre> + +<p>In the example above, <code>channel</code> is an object which holds channel metadata from the +backend server.</p> + +<h3 id="art">Present Channel and Program Information</h2> + +<p>The system TV app presents channel and program information to users as they flip through channels, +as shown in figure 1. To make sure the channel and program information works with the system TV app's +channel and program information presenter, follow the guidelines below.</p> + +<ol> +<li><strong>Channel number</strong> ({@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER}) +<li><strong>Icon</strong> +(<a href="guide/topics/manifest/application-element.html#icon"><code>android:icon</code></a> in the +TV input's manifest)</li> +<li><strong>Program description</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_SHORT_DESCRIPTION}) +<li><strong>Program title</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_TITLE})</li> +<li><strong>Channel logo</strong> ({@link android.media.tv.TvContract.Channels.Logo}) + <ul> + <li>Use the color #EEEEEE to match the surrounding text</li> + <li>Don't include padding + </ul></li> +<li><strong>Poster art</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_POSTER_ART_URI}) + <ul> + <li>Aspect ratio between 16:9 and 4:3</li> + </ul> +</ol> + +<img src="{@docRoot}images/tv/channel-info.png" id="figure1"> +<p class="img-caption"> + <strong>Figure 1.</strong> The system TV app channel and program information presenter. +</p> + +<p>The system TV app provides the same information through the program guide, including poster art, +as shown in figure 2.</p> + +<img src="{@docRoot}images/tv/prog-guide.png" id="figure2"> +<p class="img-caption"> + <strong>Figure 2.</strong> The system TV app program guide. +</p> + +<h2 id="update">Update Channel Data</h2> + +<p>When updating existing channel data, use the +{@link android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues, +java.lang.String, java.lang.String[]) update()} +method instead of deleting and re-adding the data. You can identify the current version of the data +by using {@link android.media.tv.TvContract.Channels#COLUMN_VERSION_NUMBER Channels.COLUMN_VERSION_NUMBER} +and {@link android.media.tv.TvContract.Programs#COLUMN_VERSION_NUMBER Programs.COLUMN_VERSION_NUMBER} +when choosing the records to update.</p> + +<p class="note"><strong>Note:</strong> Adding channel data to the {@link android.content.ContentProvider} +can take time. Only add current programs (those within two hours of the current time) when you update, +and use a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html">Sync Adapter</a> to +update the rest of the channel data in the background. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java"> +Android TV Live TV Sample App</a> for an example.</p> + +<h3 id="batch">Batch Loading Channel Data</h3> + +<p>When updating the system database with a large amount of channel data, use the {@link android.content.ContentResolver} +{@link android.content.ContentResolver#applyBatch applyBatch()} +or +{@link android.content.ContentResolver#bulkInsert(android.net.Uri, android.content.ContentValues[]) bulkInsert()} +method. Here's an example using {@link android.content.ContentResolver#applyBatch applyBatch()}:<p> + +<pre> +ArrayList<ContentProviderOperation> ops = new ArrayList<>(); +int programsCount = mChannelInfo.mPrograms.size(); +for (int j = 0; j < programsCount; ++j) { + ProgramInfo program = mChannelInfo.mPrograms.get(j); + ops.add(ContentProviderOperation.newInsert( + TvContract.Programs.CONTENT_URI) + .withValues(programs.get(j)) + .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS, + programStartSec * 1000) + .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS, + (programStartSec + program.mDurationSec) * 1000) + .build()); + programStartSec = programStartSec + program.mDurationSec; + if (j % 100 == 99 || j == programsCount - 1) { + try { + <strong>getContentResolver().applyBatch(TvContract.AUTHORITY, ops);</strong> + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Failed to insert programs.", e); + return; + } + ops.clear(); + } +} +</pre> + +<h3 id="async">Processing Channel Data Asynchronously</h3> + +<p>Data manipulation, such as fetching a stream from the server or accessing the database, should +not block the UI thread. Using an {@link android.os.AsyncTask} is one +way to perform updates asynchronously. For example, when loading channel info from a backend server, +you can use {@link android.os.AsyncTask} as follows:</p> + +<pre> +private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void>> { + + private Context mContext; + + public LoadTvInputTask(Context context) { + mContext = context; + } + + @Override + protected Void doInBackground(Uri... uris) { + try { + fetchUri(uris[0]); + } catch (IOException e) { + Log.d(“LoadTvInputTask”, “fetchUri error”); + } + return null; + } + + private void fetchUri(Uri videoUri) throws IOException { + InputStream inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(videoUri); + XmlPullParser parser = Xml.newPullParser(); + try { + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(inputStream, null); + sTvInput = ChannelXMLParser.parseTvInput(parser); + sSampleChannels = ChannelXMLParser.parseChannelXML(parser); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } +} +</pre> + +<p>If you need to update EPG data on a regular basis, consider using +a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html"> +Sync Adapter</a> or {@link android.app.job.JobScheduler} to run the update process during idle time, +such as every day at 3:00 a.m. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java"> +Android TV live TV sample app</a> for an example.</p> + +<p>Other techniques to separate the data update tasks from the UI thread include using the +{@link android.os.HandlerThread} class, or you may implement your own using {@link android.os.Looper} +and {@link android.os.Handler} classes. See <a href="{@docRoot}guide/components/processes-and-threads.html"> +Processes and Threads</a> for more information.</p>
\ No newline at end of file diff --git a/docs/html/training/tv/tif/index.jd b/docs/html/training/tv/tif/index.jd index 9c10850..5739294 100644 --- a/docs/html/training/tv/tif/index.jd +++ b/docs/html/training/tv/tif/index.jd @@ -1,17 +1,26 @@ page.title=Building Live TV Apps page.tags=tv, tif helpoutsWidget=true -page.article=true +startpage=true @jd:body <div id="tb-wrapper"> <div id="tb"> + <h2>Dependencies and Prerequisites</h2> + <ul> + <li>Android 5.0 (API level 21) or higher</li> + </ul> <h2>You should also read</h2> <ul> <li><a href="{@docRoot}reference/android/media/tv/package-summary.html"> android.media.tv</a></li> </ul> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> </div> </div> @@ -44,6 +53,17 @@ page.article=true Building a TV input service for your content can help make it more accessible on TV devices. </p> -<p>For more information about TV Input Framework, see the -<a href="{@docRoot}reference/android/media/tv/package-summary.html">android.media.tv</a> -reference.</p> +<h2>Topics</h2> + +<dl> + <dt><b><a href="tvinput.html">Developing a TV Input Service</a></b></dt> + <dd>Learn how to develop a TV input service, which works with the system TV app.</dd> + + <dt><b><a href="channel.html">Working with Channel Data</a></b></dt> + <dd>Learn how to describe channel and program data for the system.</dd> + + <dt><b><a href="ui.html">Managing User Interaction</a></b></dt> + <dd>Learn how to present overlays, manage content availability, and handle content selection.</dd> +</dl> + + diff --git a/docs/html/training/tv/tif/tvinput.jd b/docs/html/training/tv/tif/tvinput.jd new file mode 100644 index 0000000..91f8ded --- /dev/null +++ b/docs/html/training/tv/tif/tvinput.jd @@ -0,0 +1,177 @@ +page.title=Developing a TV Input Service +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#manifest">Declare Your TV Input Service in the Manifest</a></li> + <li><a href="#tvinput">Define Your TV Input Service</a></li> + <li><a href="#setup">Define Setup and Settings Activities</a></li> + </ol> + <h2>You should also read</h2> + <ul> + <li><a href="{@docRoot}reference/android/media/tv/package-summary.html"> + android.media.tv</a></li> + <li><a class="external-lin" href="http://source.android.com/devices/tv/index.html"> + TV Input Framework</a></li> + </ul> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>A TV input service represents a media stream source, and lets you present your media content in a +linear, broadcast TV fashion as channels and programs. With the TV input service, you can provide +parental controls, program guide information, and content ratings. The TV input service works +with the Android system TV app, developed for the device and immutable by third-party apps, which +ultimately controls and presents content on the TV. See +<a class="external-link" href="http://source.android.com/devices/tv/index.html"> +TV Input Framework</a> for more information about the framework architecture and its components.</p> + +<p>To develop a TV input service, you implement the following components:</p> + +<ul> + <li>{@link android.media.tv.TvInputService} provides long-running and background availability for + the TV input</li> + <li>{@link android.media.tv.TvInputService.Session} maintains the TV input state and communicates + with the hosting app</li> + <li>{@link android.media.tv.TvContract} describes the channels and programs available to the TV + input</li> + <li>{@link android.media.tv.TvContract.Channels} represents information about a TV channel</li> + <li>{@link android.media.tv.TvContract.Programs} describes a TV program with data such as program + title and start time</li> + <li>{@link android.media.tv.TvTrackInfo} represents an audio, video, or subtitle track</li> + <li>{@link android.media.tv.TvContentRating} describes a content rating, allows for custom content + rating schemes</li> + <li>{@link android.media.tv.TvInputManager} provides an API to the system TV app and manages + the interaction with TV inputs and apps</li> +</ul> + +<h2 id="manifest">Declare Your TV Input Service in the Manifest</h2> + +<p>Your app manifest must declare your {@link android.media.tv.TvInputService}. Within that +declaration, specify the {@link android.Manifest.permission#BIND_TV_INPUT} permission to allow the +service to connect the TV input to the system. A system service (<code>TvInputManagerService</code>) +performs the binding and has that permission. The system TV app sends requests to TV input services +via the {@link android.media.tv.TvInputManager} interface. The service declaration must also +include an intent filter that specifies the {@link android.media.tv.TvInputService} +as the action to perform with the intent. Also within the service declaration, declare the service +meta data in a separate XML resource. The service declaration, the intent filter and the service +meta data are described in the following example.</p> + +<pre> +<service android:name="com.example.sampletvinput.SampleTvInput" + android:label="@string/sample_tv_input_label" + android:permission="android.permission.BIND_TV_INPUT"> + <intent-filter> + <action android:name="android.media.tv.TvInputService" /> + </intent-filter> + <meta-data android:name="android.media.tv.input" + android:resource="@xml/sample_tv_input" /> +</service> +</pre> + +<p>Define the service meta data in separate XML file, as shown in the following example. The service +meta data must include a setup interface that describes the TV input's initial configuration and +channel scan. Also, the service meta data may (optionally) describe a settings activity for users to +modify the TV input's behavior. The service meta data file is located in the XML resources directory +for your application and must match the name of the resource in the manifest. Using the example +manifest entries above, you would create an XML file in the location +<code>res/xml/sample_tv_input.xml</code>, with the following contents:</p> + +<pre> +<tv-input xmlns:android="http://schemas.android.com/apk/res/android" + <!-- Required: activity for setting up the input --> + android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" + <!-- Optional: activity for controlling the settings --> + android:settingsActivity="com.example.sampletvinput.SampleTvInputSettingsActivity" /> +</pre> + +<h2 id="tvinput">Define Your TV Input Service</h2> + +<div class="figure"> +<img id="tvinputlife" src="{@docRoot}images/tv/tvinput-life.png" alt=""/> +<p class="img-caption"><strong>Figure 1.</strong>TvInputService lifecycle.</p> +</div> + +<p>For your service, you extend the {@link android.media.tv.TvInputService} class. A +{@link android.media.tv.TvInputService} implementation is a +<a href="{@docRoot}guide/components/bound-services.html">bound service</a> where the system service +(<code>TvInputManagerService</code>) is the client that binds to it. The service life cycle methods +you need to implement are illustrated in figure 1.</p> + +<p>The {@link android.app.Service#onCreate()} method initializes and starts the +{@link android.os.HandlerThread} which provides a process thread separate from the UI thread to +handle system-driven actions. In the following example, the {@link android.app.Service#onCreate()} +method initializes the {@link android.view.accessibility.CaptioningManager} and prepares to handle +the {@link android.media.tv.TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} +and {@link android.media.tv.TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} actions. These +actions describe system intents fired when the user changes the parental control settings, and when +there is a change on the list of blocked ratings.</p> + +<pre> +@Override +public void onCreate() { + super.onCreate(); + mHandlerThread = new HandlerThread(getClass() + .getSimpleName()); + mHandlerThread.start(); + mDbHandler = new Handler(mHandlerThread.getLooper()); + mHandler = new Handler(); + mCaptioningManager = (CaptioningManager) + getSystemService(Context.CAPTIONING_SERVICE); + + setTheme(android.R.style.Theme_Holo_Light_NoActionBar); + + mSessions = new ArrayList<BaseTvInputSessionImpl>(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TvInputManager + .ACTION_BLOCKED_RATINGS_CHANGED); + intentFilter.addAction(TvInputManager + .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); + registerReceiver(mBroadcastReceiver, intentFilter); +} +</pre> + +<p> See <a href="{@docRoot}training/tv/tif/ui.html#control"> +Control Content</a> for more information about working with blocked content and providing +parental control. See {@link android.media.tv.TvInputManager} for more system-driven actions that +you may want to handle in your TV input service.</p> + +<p>The {@link android.media.tv.TvInputService} creates a +{@link android.media.tv.TvInputService.Session} that implements {@link android.os.Handler.Callback} +to handle player state changes. With {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) onSetSurface()}, +the {@link android.media.tv.TvInputService.Session} sets the {@link android.view.Surface} with the +video content. See <a href="{@docRoot}training/tv/tif/ui.html#surface">Integrate Player with Surface</a> +for more information about working with {@link android.view.Surface} to render video.</p> + +<p>The {@link android.media.tv.TvInputService.Session} handles the +{@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) onTune()} +event when the user selects a channel, and notifies the system TV app for changes in the content and +content meta data. These <code>notify()</code>code> methods are described in +<a href="{@docRoot}training/tv/tif/ui.html#control"> +Control Content</a> and <a href="training/tv/tif/ui.html#track">Handle Track Selection</a> further +in this training.</p> + +<h2 id="setup">Define Setup and Settings Activities</h2> + +<p>The system TV app works with the setup and settings activities you define for your TV input. The +setup activity is required and must provide at least one channel record for the system database. The +system TV app will invoke the setup activity when it cannot find a channel for the TV input. +<p>The setup activity describes to the system TV app the channels made available through the TV +input, as demonstrated in the next lesson, <a href="{@docRoot}training/tv/tif/channel.html">Creating +and Updating Channel Data</a>.</p> + +<p>The settings activity is optional. You can define a settings activity to turn on parental +controls, enable closed captions, set the display attributes, and so forth.</p> + + diff --git a/docs/html/training/tv/tif/ui.jd b/docs/html/training/tv/tif/ui.jd new file mode 100644 index 0000000..6ead3db --- /dev/null +++ b/docs/html/training/tv/tif/ui.jd @@ -0,0 +1,304 @@ +page.title=Managing User Interaction +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#surface">Integrate Player with Surface</a></li> + <li><a href="#overlay">Use an Overlay</a></li> + <li><a href="#control">Control Content</a></li> + <li><a href="#track">Handle Track Selection</a></li> + </ol> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>In the live TV experience the user changes channels and is presented with +channel and program information briefly before the information disappears. Other types of information, +such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV +app, such information should not interfere with the program content playing on the screen.</p> + +<img src="{@docRoot}images/tv/do-not-attempt.png" id="figure1"> +<p class="img-caption"> + <strong>Figure 1.</strong> An overlay message in a live TV app. +</p> + +<p>Also consider whether certain program content should be presented, given the +content's rating and parental control settings, and how your app behaves and informs the user when +content is blocked or unavailable. This lesson describes how to develop your TV input's user +experience for these considerations.</p> + +<h2 id="surface">Integrate Player with Surface</h2> + +<p>Your TV input must render video onto a {@link android.view.Surface} object, which is passed by +the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()} +method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing +content in the {@link android.view.Surface} object:</p> + +<pre> +@Override +public boolean onSetSurface(Surface surface) { + if (mPlayer != null) { + mPlayer.setSurface(surface); + } + mSurface = surface; + return true; +} + +@Override +public void onSetStreamVolume(float volume) { + if (mPlayer != null) { + mPlayer.setVolume(volume, volume); + } + mVolume = volume; +} +</pre> + +<p>Similarly, here's how to do it using <a href="{@docRoot}guide/topics/media/exoplayer.html"> +ExoPlayer</a>:</p> + +<pre> +@Override +public boolean onSetSurface(Surface surface) { + if (mPlayer != null) { + mPlayer.sendMessage(mVideoRenderer, + MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, + surface); + } + mSurface = surface; + return true; +} + +@Override +public void onSetStreamVolume(float volume) { + if (mPlayer != null) { + mPlayer.sendMessage(mAudioRenderer, + MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, + volume); + } + mVolume = volume; +} +</pre> + +<h2 id="overlay">Use an Overlay</h2> + +<p>Use an overlay to display subtitles, messages, ads or MHEG-5 data broadcasts. By default, the +overlay is disabled. You can enable it when you create the session by calling +{@link android.media.tv.TvInputService.Session#setOverlayViewEnabled(boolean) TvInputService.Session.setOverlayViewEnabled(true)}, +as in the following example:</p> + +<pre> +@Override +public final Session onCreateSession(String inputId) { + BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); + session.setOverlayViewEnabled(true); + mSessions.add(session); + return session; +} +</pre> + +<p>Use a {@link android.view.View} object for the overlay, returned from {@link android.media.tv.TvInputService.Session#onCreateOverlayView() TvInputService.Session.onCreateOverlayView()}, as shown here:</p> + +<pre> +@Override +public View onCreateOverlayView() { + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.overlayview, null); + mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles); + + // Configure the subtitle view. + CaptionStyleCompat captionStyle; + float captionTextSize = getCaptionFontSize(); + captionStyle = CaptionStyleCompat.createFromCaptionStyle( + mCaptioningManager.getUserStyle()); + captionTextSize *= mCaptioningManager.getFontScale(); + mSubtitleView.setStyle(captionStyle); + mSubtitleView.setTextSize(captionTextSize); + return view; +} +</pre> + +<p>The layout definition for the overlay might look something like this:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.exoplayer.text.SubtitleView + android:id="@+id/subtitles" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center_horizontal" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="32dp" + android:visibility="invisible"/> +</FrameLayout> +</pre> + +<h2 id="control">Control Content</h2> + +<p>When the user selects a channel, your TV input handles the {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) +onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV +app's parental controls determine what content displays, given the content rating. +The following sections describe how to manage channel and program selection using the +{@link android.media.tv.TvInputService.Session} <code>notify</code> methods that +communicate with the system TV app.</p> + +<h3 id="unavailable">Make Video Unavailable</h3> + +<p>When the user changes the channel, you want to make sure the screen doesn't display any stray +video artifacts before your TV input renders the content. When you call {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) TvInputService.Session.onTune()}, +you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()} +and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as +shown in the following example.</p> + +<pre> +@Override +public boolean onTune(Uri channelUri) { + if (mSubtitleView != null) { + mSubtitleView.setVisibility(View.INVISIBLE); + } + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + mUnblockedRatingSet.clear(); + + mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable); + mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); + mDbHandler.post(mPlayCurrentProgramRunnable); + return true; +} +</pre> + +<p>Then, when the content is rendered to the {@link android.view.Surface}, you call +{@link android.media.tv.TvInputService.Session#notifyVideoAvailable() TvInputService.Session.notifyVideoAvailable()} +to allow the video to display, like so:</p> + +<pre> +@Override +public void onDrawnToSurface(Surface surface) { + mFirstFrameDrawn = true; + notifyVideoAvailable(); +} +</pre> + +<p>This transition lasts only for fractions of a second, but presenting a blank screen is +visually better than allowing the picture to flash odd blips and jitters.</p> + +<p>See also, <a href="#surface">Integrate Player with Surface</a> for more information about working +with {@link android.view.Surface} to render video.</p> + +<h3 id="parental">Provide Parental Control</h3> + +<p>To determine if a given content is blocked by parental controls and content rating, you check the +{@link android.media.tv.TvInputManager} class methods, {@link android.media.tv.TvInputManager#isParentalControlsEnabled()} +and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You +might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a +set of currently allowed content ratings. These considerations are shown in the following sample.</p> + +<pre> +private void checkContentBlockNeeded() { + if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled() + || !mTvInputManager.isRatingBlocked(mCurrentContentRating) + || mUnblockedRatingSet.contains(mCurrentContentRating)) { + // Content rating is changed so we don't need to block anymore. + // Unblock content here explicitly to resume playback. + unblockContent(null); + return; + } + + mLastBlockedRating = mCurrentContentRating; + if (mPlayer != null) { + // Children restricted content might be blocked by TV app as well, + // but TIF should do its best not to show any single frame of blocked content. + releasePlayer(); + } + + notifyContentBlocked(mCurrentContentRating); +} +</pre> + +<p>Once you have determined if the content should or should not be blocked, notify the system TV +app by calling the +{@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()} +or +{@link android.media.tv.TvInputService.Session#notifyContentBlocked(android.media.tv.TvContentRating) notifyContentBlocked()} +, as shown in the previous example.</p> + +<p>Use the {@link android.media.tv.TvContentRating} class to generate the system-defined string for +the {@link android.media.tv.TvContract.Programs#COLUMN_CONTENT_RATING} with the +<code><a href="{@docRoot}reference/android/media/tv/TvContentRating.html#createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...)">TvContentRating.createRating()</a></code> +method, as shown here:</p> + +<pre> +TvContentRating rating = TvContentRating.createRating( + "com.android.tv", + "US_TV", + "US_TV_PG", + "US_TV_D", "US_TV_L"); +</pre> + +<h2 id="track">Handle Track Selection</h2> + +<p>The {@link android.media.tv.TvTrackInfo} class holds information about media tracks such +as the track type (video, audio, or subtitle) and so forth. </p> + +<p>The first time your TV input session is able to get track information, it should call +<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">TvInputService.Session.notifyTracksChanged()</a></code> with a list of all tracks to update the system TV app. When there +is a change in track information, call +<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">notifyTracksChanged()</a></code> +again to update the system. + +</p> + +<p>The system TV app provides an interface for the user to select a specific track if more than one +track is available for a given track type; for example, subtitles in different languages. Your TV +input responds to the +{@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()} +call from the system TV app by calling +{@link android.media.tv.TvInputService.Session#notifyTrackSelected(int, java.lang.String) notifyTrackSelected()} +, as shown in the following example. Note that when <code>null</code> +is passed as the track ID, this <em>deselects</em> the track.</p> + +<pre> +@Override +public boolean onSelectTrack(int type, String trackId) { + if (mPlayer != null) { + if (type == TvTrackInfo.TYPE_SUBTITLE) { + if (!mCaptionEnabled && trackId != null) { + return false; + } + mSelectedSubtitleTrackId = trackId; + if (trackId == null) { + mSubtitleView.setVisibility(View.INVISIBLE); + } + } + if (mPlayer.selectTrack(type, trackId)) { + notifyTrackSelected(type, trackId); + return true; + } + } + return false; +} +</pre> + + + + + + + diff --git a/docs/html/training/wearables/apps/voice.jd b/docs/html/training/wearables/apps/voice.jd index 6d49319..3aef3c4 100644 --- a/docs/html/training/wearables/apps/voice.jd +++ b/docs/html/training/wearables/apps/voice.jd @@ -131,6 +131,17 @@ named <code>MyNoteActivity</code>: </tr> <tr> + <td>Start stopwatch</td> + <td>"Ok Google, start stopwatch"</td> + <td> + <dl> + <dt>Action</dt> + <dd><code>com.google.android.wearable.action.STOPWATCH</code></dd> + </dl> + </td> + </tr> + + <tr> <td>Start/Stop a bike ride</td> <td>"OK Google, start cycling"<br/><br/>"OK Google, start my bike ride"<br/><br/>"OK Google, stop cycling"</td> <td> diff --git a/docs/html/training/wearables/watch-faces/drawing.jd b/docs/html/training/wearables/watch-faces/drawing.jd index 3c5da34..60da5d5 100644 --- a/docs/html/training/wearables/watch-faces/drawing.jd +++ b/docs/html/training/wearables/watch-faces/drawing.jd @@ -377,7 +377,8 @@ public void onPropertiesChanged(Bundle properties) { <ul> <li>For devices that use low-bit ambient mode, the screen supports fewer bits for each color -in ambient mode, so you should disable anti-aliasing.</li> +in ambient mode, so you should disable anti-aliasing and bitmap filtering when the device switches +to ambient mode.</li> <li>For devices that require burn-in protection, avoid using large blocks of white pixels in ambient mode and do not place content within 10 pixels of the edge of the screen, since the system shifts the content periodically to avoid pixel burn-in.</li> @@ -385,7 +386,9 @@ system shifts the content periodically to avoid pixel burn-in.</li> <p>For more information about low-bit ambient mode and burn-in protection, see <a href="{@docRoot}design/wear/watchfaces.html#SpecialScreens">Optimize for Special -Screens</a>.</p> +Screens</a>. For more information on how to disable bitmap filtering, see +<a href="{@docRoot}training/wearables/watch-faces/performance.html#BitmapFiltering">Bitmap +Filtering</a>.</p> <h2 id="Modes">Respond to Changes Between Modes</h2> diff --git a/docs/html/training/wearables/watch-faces/performance.jd b/docs/html/training/wearables/watch-faces/performance.jd index 68438fe..118bc6a 100644 --- a/docs/html/training/wearables/watch-faces/performance.jd +++ b/docs/html/training/wearables/watch-faces/performance.jd @@ -99,7 +99,9 @@ enabled (right).</p> setFilterBitmap()} method. <a href="#fig2">Figure 2</a> shows a magnified view of a clock hand with and without bitmap filtering.</p> - +<p class="note"><strong>Note:</strong> In low-bit ambient mode, the system does not reliably +render the colors in the image for bitmap filtering to process successfully. When ambient mode is +active, disable bitmap filtering.</p> <h2 id="OutDrawing">Move Expensive Operations Outside the Drawing Method</h2> |
