ตัวอย่างการทดสอบ TF แบบ end-to-end

บทช่วยสอนนี้จะแนะนำคุณตลอดการสร้างการกำหนดค่าการทดสอบของสหพันธ์การค้า (Tradefed หรือ TF) "สวัสดีชาวโลก" และให้คำแนะนำเบื้องต้นเกี่ยวกับกรอบงาน TF แบบลงมือปฏิบัติจริง เริ่มต้นจากสภาพแวดล้อมการพัฒนา คุณจะสร้างการกำหนดค่าที่เรียบง่ายและเพิ่มคุณสมบัติต่างๆ

บทช่วยสอนนี้นำเสนอกระบวนการพัฒนาการทดสอบเป็นชุดแบบฝึกหัด แต่ละบทประกอบด้วยหลายขั้นตอน ซึ่งสาธิตวิธีการสร้างและค่อยๆ ปรับแต่งการกำหนดค่าของคุณ มีโค้ดตัวอย่างทั้งหมดที่คุณต้องใช้ในการกำหนดค่าการทดสอบให้ครบถ้วน และชื่อของแบบฝึกหัดแต่ละรายการจะมีคำอธิบายประกอบด้วยตัวอักษรที่อธิบายบทบาทที่เกี่ยวข้องในขั้นตอนนั้น:

  • D สำหรับนักพัฒนา
  • ฉัน สำหรับ Integrator
  • R สำหรับนักวิ่งทดสอบ

หลังจากเสร็จสิ้นบทช่วยสอน คุณจะมีการกำหนดค่า TF ที่ใช้งานได้ และเข้าใจแนวคิดที่สำคัญมากมายในกรอบงาน TF

จัดตั้งสหพันธ์การค้า

สำหรับรายละเอียดเกี่ยวกับการตั้งค่าสภาพแวดล้อมการพัฒนา TF โปรดดู การตั้งค่าเครื่อง ส่วนที่เหลือของบทช่วยสอนนี้จะถือว่าคุณมีเชลล์ที่เปิดอยู่ซึ่งได้รับการเตรียมใช้งานกับสภาพแวดล้อม TF

เพื่อความง่าย บทช่วยสอนนี้จะแสดงการเพิ่มการกำหนดค่าและคลาสลงในไลบรารีหลักของเฟรมเวิร์ก TF สิ่งนี้สามารถขยายไปสู่การพัฒนาโมดูลภายนอกแผนผังต้นทางโดยการคอมไพล์ JAR ที่แลกเปลี่ยนแล้ว จากนั้นคอมไพล์โมดูลของคุณกับ JAR นั้น

สร้างคลาสทดสอบ (D)

มาสร้างการทดสอบสวัสดีโลกที่เพิ่งทิ้งข้อความไปที่ stdout โดยทั่วไปการทดสอบแบบ tradefed จะใช้อินเทอร์เฟซ IRemoteTest นี่คือการใช้งานสำหรับ HelloWorldTest:

package com.android.tradefed.example;

import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IRemoteTest;

public class HelloWorldTest implements IRemoteTest {
    @Override
    public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
        CLog.i("Hello, TF World!");
    }
}

บันทึกโค้ดตัวอย่างนี้ไปที่ <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java และสร้าง tradefed ใหม่จากเชลล์ของคุณ:

m -jN

โปรดทราบว่า CLog.i ในตัวอย่างด้านบนใช้เพื่อกำหนดเอาต์พุตไปยังคอนโซลโดยตรง ข้อมูลเพิ่มเติมเกี่ยวกับการเข้าสู่ระบบในสหพันธ์การค้ามีอธิบายไว้ใน การบันทึก (D, I, R)

หากการสร้างไม่สำเร็จ ให้ปรึกษา การตั้งค่าเครื่อง เพื่อให้แน่ใจว่าคุณไม่พลาดขั้นตอนใดขั้นตอนหนึ่ง

สร้างการกำหนดค่า (I)

การทดสอบของ Trade Federation สามารถเรียกใช้งานได้โดยการสร้าง Configuration ซึ่งเป็นไฟล์ XML ที่จะสั่งให้ tradefed ทดสอบ (หรือทดสอบ) ใดที่จะรัน รวมถึงโมดูลอื่น ๆ ที่จะดำเนินการและในลำดับใด

มาสร้างการกำหนดค่าใหม่สำหรับ HelloWorldTest ของเรา (จดชื่อคลาสเต็มของ HelloWorldTest):

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
</configuration>

บันทึกข้อมูลนี้ลงในไฟล์ helloworld.xml ที่ใดก็ได้บนระบบไฟล์ในเครื่องของคุณ (เช่น /tmp/helloworld.xml ) TF จะแยกวิเคราะห์ไฟล์ XML การกำหนดค่า (aka config ) โหลดคลาสที่ระบุโดยใช้การสะท้อน สร้างอินสแตนซ์ ส่งไปที่ IRemoteTest และเรียกใช้วิธี run

เรียกใช้การกำหนดค่า (R)

จากเชลล์ของคุณ เปิดคอนโซลที่แลกเปลี่ยนแล้ว:

tradefed.sh

ตรวจสอบให้แน่ใจว่าอุปกรณ์เชื่อมต่อกับเครื่องโฮสต์และสามารถมองเห็นการแลกเปลี่ยนได้:

tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

การกำหนดค่าสามารถดำเนินการได้โดยใช้คำสั่งคอนโซล run <config> พยายาม:

tf> run /tmp/helloworld.xml
05-12 13:19:36 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

คุณควรเห็น "Hello, TF World!" เอาต์พุตบนเทอร์มินัล

คุณสามารถยืนยันได้ว่าคำสั่งทำงานเสร็จแล้วโดยใช้ list invocations หรือ li ในพรอมต์คอนโซล และไม่ควรพิมพ์อะไรเลย หากคำสั่งกำลังทำงานอยู่ คำสั่งจะแสดงดังนี้:

tf >l i
Command Id  Exec Time  Device       State
10          0m:00      [876X00GNG]  running stub on build(s) 'BuildInfo{bid=0, target=stub, serial=876X00GNG}'

เพิ่มการกำหนดค่าให้กับ classpath (D, I, R)

เพื่อความสะดวกในการปรับใช้ คุณยังสามารถรวมการกำหนดค่าเข้ากับ JAR ที่แลกเปลี่ยนด้วยตนเองได้ Tradefed จะจดจำการกำหนดค่าทั้งหมดที่อยู่ในโฟลเดอร์ กำหนดค่า บน classpath โดยอัตโนมัติ

เพื่อแสดงให้เห็น ให้ย้ายไฟล์ helloworld.xml ไปยังไลบรารีหลักที่ tradefed ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml ) สร้าง tradefed ใหม่ รีสตาร์ทคอนโซล tradefed จากนั้นขอให้ tradefed แสดงรายการการกำหนดค่าจาก classpath:

tf> list configs
[…]
example/helloworld: Runs the hello world test

ตอนนี้คุณสามารถรันการกำหนดค่า helloworld โดยใช้:

tf> run example/helloworld
05-12 13:21:21 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

โต้ตอบกับอุปกรณ์ (D, R)

จนถึงตอนนี้ HelloWorldTest ของเราไม่ได้ทำอะไรที่น่าสนใจ ความเชี่ยวชาญพิเศษของ Tradefed คือการทดสอบโดยใช้อุปกรณ์ Android ดังนั้นมาเพิ่มอุปกรณ์ Android ลงในการทดสอบกันดีกว่า

การทดสอบสามารถรับการอ้างอิงไปยังอุปกรณ์ Android ได้โดยใช้ TestInformation ซึ่งจัดทำโดยเฟรมเวิร์กเมื่อมีการเรียกใช้เมธอด IRemoteTest#run

มาแก้ไขข้อความพิมพ์ HelloWorldTest เพื่อแสดงหมายเลขซีเรียลของอุปกรณ์:

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());
}

ตอนนี้สร้างการแลกเปลี่ยนใหม่และตรวจสอบรายการอุปกรณ์:

tradefed.sh
tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

จดหมายเลขซีเรียลที่แสดงเป็น Available ; นั่นคืออุปกรณ์ที่ควรจัดสรรให้กับ HelloWorld:

tf> run example/helloworld
05-12 13:26:18 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548

คุณควรเห็นข้อความพิมพ์ใหม่ที่แสดงหมายเลขซีเรียลของอุปกรณ์

ส่งผลการทดสอบ (D)

IRemoteTest รายงานผลลัพธ์โดยการเรียกเมธอดบนอินสแตนซ์ ITestInvocationListener ที่ระบุให้กับเมธอด #run กรอบงาน TF มีหน้าที่รายงานการเริ่มต้น (ผ่าน ITestInvocationListener#invocationStarted ) และสิ้นสุด (ผ่าน ITestInvocationListener#invocationEnded ) ของแต่ละ Invocation

การ ทดสอบการทำงาน คือการรวบรวมการทดสอบเชิงตรรกะ เพื่อรายงานผลการทดสอบ IRemoteTest มีหน้าที่รับผิดชอบในการรายงานการเริ่มต้นของการทดสอบ การเริ่มต้นและสิ้นสุดของการทดสอบแต่ละครั้ง และการสิ้นสุดของการทดสอบ

ต่อไปนี้คือลักษณะการใช้งาน HelloWorldTest หากผลการทดสอบล้มเหลวเพียงครั้งเดียว

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());

    TestDescription testId = new TestDescription("com.example.TestClassName", "sampleTest");
    listener.testRunStarted("helloworldrun", 1);
    listener.testStarted(testId);
    listener.testFailed(testId, "oh noes, test failed");
    listener.testEnded(testId, Collections.emptyMap());
    listener.testRunEnded(0, Collections.emptyMap());
}

TF มีการใช้งาน IRemoteTest หลายอย่างที่คุณสามารถนำมาใช้ซ้ำได้ แทนที่จะเขียนด้วยตัวเองตั้งแต่เริ่มต้น ตัวอย่างเช่น InstrumentationTest สามารถเรียกใช้การทดสอบแอปพลิเคชัน Android จากระยะไกลบนอุปกรณ์ Android แยกวิเคราะห์ผลลัพธ์ และส่งต่อผลลัพธ์เหล่านั้นไปยัง ITestInvocationListener ) สำหรับรายละเอียด โปรดดู ประเภทการทดสอบ

เก็บผลการทดสอบ (I)

การใช้งานตัวฟังการทดสอบเริ่มต้นสำหรับการกำหนดค่า TF คือ TextResultReporter ซึ่งจะทิ้งผลลัพธ์ของการเรียกใช้ไปยัง stdout เพื่อแสดงให้เห็น ให้รันการกำหนดค่า HelloWorldTest จากส่วนก่อนหน้า:

./tradefed.sh
tf> run example/helloworld
04-29 18:25:55 I/TestInvocation: Invocation was started with cmd: /tmp/helloworld.xml
04-29 18:25:55 I/TestInvocation: Starting invocation for 'stub' with '[ BuildInfo{bid=0, target=stub, serial=876X00GNG} on device '876X00GNG']
04-29 18:25:55 I/HelloWorldTest: Hello, TF World! I have device 876X00GNG
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Running helloworldrun: 1 tests
04-29 18:25:55 W/InvocationToJUnitResultForwarder:
Test com.example.TestClassName#sampleTest failed with stack:
 oh noes, test failed
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Run ended in 0 ms

หากต้องการจัดเก็บผลลัพธ์ของการร้องขอไว้ที่อื่น เช่น ในไฟล์ ให้ระบุการใช้งาน ITestInvocationListener แบบกำหนดเองโดยใช้แท็ก result_reporter ในการกำหนดค่าของคุณ

TF ยังมี XmlResultReporter Listener ซึ่งเขียนผลการทดสอบลงในไฟล์ XML ในรูปแบบที่คล้ายกับที่ใช้โดยตัวเขียน ant JUnit XML หากต้องการระบุ result_reporter ในการกำหนดค่า ให้แก้ไขการกำหนดค่า …/res/config/example/helloworld.xml :

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
</configuration>

ตอนนี้สร้างการแลกเปลี่ยนใหม่และรันตัวอย่าง Hello World อีกครั้ง:

tf> run example/helloworld
05-16 21:07:07 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548
05-16 21:07:07 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_2991649128735283633/device_logcat_6999997036887173857.txt
05-16 21:07:07 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_2991649128735283633/host_log_6307746032218561704.txt
05-16 21:07:07 I/XmlResultReporter: XML test result file generated at /tmp/0/inv_2991649128735283633/test_result_536358148261684076.xml. Total tests 1, Failed 1, Error 0

สังเกตข้อความบันทึกที่ระบุว่าไฟล์ XML ถูกสร้างขึ้น ไฟล์ที่สร้างขึ้นควรมีลักษณะดังนี้:

<?xml version='1.0' encoding='UTF-8' ?>
<testsuite name="stub" tests="1" failures="1" errors="0" time="9" timestamp="2011-05-17T04:07:07" hostname="localhost">
  <properties />
  <testcase name="sampleTest" classname="com.example.TestClassName" time="0">
    <failure>oh noes, test failed
    </failure>
  </testcase>
</testsuite>

คุณยังสามารถเขียน Listener คำขอที่คุณกำหนดเองได้ โดยเพียงแค่ต้องใช้อินเทอร์เฟซ ITestInvocationListener

Tradefed รองรับ Listener คำขอหลายรายการ ดังนั้นคุณจึงสามารถส่งผลการทดสอบไปยังปลายทางอิสระหลายแห่งได้ ในการดำเนินการนี้ เพียงระบุแท็ก <result_reporter> หลายแท็กในการกำหนดค่าของคุณ

สิ่งอำนวยความสะดวกในการบันทึก (D, I, R)

สิ่งอำนวยความสะดวกการบันทึกของ TF รวมถึงความสามารถในการ:

  1. บันทึกบันทึกจากอุปกรณ์ (หรือที่เรียกว่าอุปกรณ์ logcat)
  2. บันทึกบันทึกจากเฟรมเวิร์กของสหพันธ์การค้าที่ทำงานบนเครื่องโฮสต์ (หรือที่เรียกว่าบันทึกของโฮสต์)

กรอบงาน TF จะจับ logcat จากอุปกรณ์ที่จัดสรรโดยอัตโนมัติและส่งไปยัง Listener การร้องขอเพื่อประมวลผล จากนั้น XmlResultReporter จะบันทึก logcat ของอุปกรณ์ที่บันทึกเป็นไฟล์

บันทึกโฮสต์ TF ได้รับการรายงานโดยใช้ CLog wrapper สำหรับคลาส ddmlib Log มาแปลงการเรียก System.out.println ก่อนหน้าใน HelloWorldTest เป็นการเรียก CLog :

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device %s", getDevice().getSerialNumber());

CLog จัดการการแก้ไขสตริงโดยตรง คล้ายกับ String.format เมื่อคุณสร้างและรัน TF อีกครั้ง คุณจะเห็นข้อความบันทึกบน stdout:

tf> run example/helloworld
…
05-16 21:30:46 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548
…

ตามค่าเริ่มต้น tradefed จะส่งข้อความบันทึกของโฮสต์ไปที่ stdout TF ยังมีการใช้งานบันทึกที่เขียนข้อความไปยังไฟล์: FileLogger หากต้องการเพิ่มการบันทึกไฟล์ ให้เพิ่มแท็ก logger ลงในการกำหนดค่า โดยระบุชื่อคลาสเต็มของ FileLogger :

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
    <logger class="com.android.tradefed.log.FileLogger" />
</configuration>

ตอนนี้ สร้างและรันตัวอย่าง helloworld อีกครั้ง:

tf >run example/helloworld
…
05-16 21:38:21 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_6390011618174565918/device_logcat_1302097394309452308.txt
05-16 21:38:21 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt
…

ข้อความบันทึกระบุเส้นทางของบันทึกโฮสต์ ซึ่งเมื่อดูแล้วควรมีข้อความบันทึก HelloWorldTest ของคุณ:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

ตัวอย่างผลลัพธ์:

…
05-16 21:38:21 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

ตัวเลือกการจัดการ (D, I, R)

ออบเจ็กต์ที่โหลดจากการกำหนดค่า TF (หรือที่เรียกว่า ออบ เจ็กต์การกำหนดค่า ) ยังสามารถรับข้อมูลจากอาร์กิวเมนต์บรรทัดคำสั่งผ่านการใช้คำอธิบายประกอบ @Option

ในการเข้าร่วม คลาสอ็อบเจ็กต์การกำหนดค่าจะใช้คำอธิบายประกอบ @Option กับฟิลด์สมาชิกและระบุชื่อที่ไม่ซ้ำกัน ซึ่งช่วยให้สามารถเติมค่าฟิลด์สมาชิกนั้นผ่านตัวเลือกบรรทัดคำสั่ง (และยังเพิ่มตัวเลือกนั้นให้กับระบบวิธีใช้การกำหนดค่าโดยอัตโนมัติอีกด้วย)

หมายเหตุ: รองรับฟิลด์บางประเภทเท่านั้น สำหรับคำอธิบายประเภทที่รองรับ โปรดดู OptionSetter

มาเพิ่ม @Option ให้กับ HelloWorldTest:

@Option(name="my_option",
        shortName='m',
        description="this is the option's help text",
        // always display this option in the default help text
        importance=Importance.ALWAYS)
private String mMyOption = "thisisthedefault";

ต่อไป เรามาเพิ่มข้อความบันทึกเพื่อแสดงค่าของตัวเลือกใน HelloWorldTest เพื่อให้เราสามารถแสดงให้เห็นว่าได้รับอย่างถูกต้อง:

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    …
    CLog.logAndDisplay(LogLevel.INFO, "I received option '%s'", mMyOption);

สุดท้าย สร้าง TF ใหม่และเรียกใช้ helloworld คุณควรเห็นข้อความบันทึกพร้อมค่าเริ่มต้น my_option :

tf> run example/helloworld
…
05-24 18:30:05 I/HelloWorldTest: I received option 'thisisthedefault'

ส่งผ่านค่าจากบรรทัดคำสั่ง

ส่งผ่านค่าสำหรับ my_option ; คุณควรเห็น my_option ที่เติมค่านั้น:

tf> run example/helloworld --my_option foo
…
05-24 18:33:44 I/HelloWorldTest: I received option 'foo'

การกำหนดค่า TF ยังมีระบบวิธีใช้ซึ่งจะแสดงข้อความช่วยเหลือสำหรับช่อง @Option โดยอัตโนมัติ ลองตอนนี้แล้วคุณจะเห็นข้อความช่วยเหลือสำหรับ my_option :

tf> run example/helloworld --help
Printing help for only the important options. To see help for all options, use the --help-all flag

  cmd_options options:
    --[no-]help          display the help text for the most important/critical options. Default: false.
    --[no-]help-all      display the full help text for all options. Default: false.
    --[no-]loop          keep running continuously. Default: false.

  test options:
    -m, --my_option      this is the option's help text Default: thisisthedefault.

  'file' logger options:
    --log-level-display  the minimum log level to display on stdout. Must be one of verbose, debug, info, warn, error, assert. Default: error.

สังเกตข้อความเกี่ยวกับ "การพิมพ์เฉพาะตัวเลือกที่สำคัญ" เพื่อลดความยุ่งเหยิงในการช่วยเหลือตัวเลือก TF จะใช้แอตทริบิวต์ Option#importance เพื่อพิจารณาว่าจะแสดงข้อความช่วยเหลือฟิลด์ @Option เฉพาะหรือไม่ เมื่อระบุ --help --help-all จะแสดงความช่วยเหลือสำหรับช่อง @Option ทั้งหมดเสมอ โดยไม่คำนึงถึงความสำคัญ สำหรับรายละเอียด โปรดดูที่ Option.Importance

ส่งผ่านค่าจากการกำหนดค่า

คุณยังสามารถระบุค่าตัวเลือกภายในการกำหนดค่าได้โดยการเพิ่มองค์ประกอบ <option name="" value=""> ทดสอบโดยใช้ helloworld.xml :

<test class="com.android.tradefed.example.HelloWorldTest" >
    <option name="my_option" value="fromxml" />
</test>

การสร้างและใช้งาน helloworld อีกครั้งควรสร้างผลลัพธ์นี้:

05-24 20:38:25 I/HelloWorldTest: I received option 'fromxml'

วิธีใช้การกำหนดค่าควรอัปเดตเพื่อระบุค่าเริ่มต้นของ my_option :

tf> run example/helloworld --help
  test options:
    -m, --my_option      this is the option's help text Default: fromxml.

ออบเจ็กต์การกำหนดค่าอื่น ๆ ที่รวมอยู่ในการกำหนดค่า helloworld เช่น FileLogger ก็ยอมรับตัวเลือกเช่นกัน ตัวเลือก --log-level-display นั้นน่าสนใจเพราะมันกรองบันทึกที่แสดงบน stdout ก่อนหน้านี้ในบทช่วยสอน คุณอาจสังเกตเห็นข้อความบันทึก "Hello, TF World! ฉันมีอุปกรณ์ …' หยุดแสดงบน stdout หลังจากที่เราเปลี่ยนมาใช้ FileLogger คุณสามารถเพิ่มความละเอียดของการบันทึกเป็น stdout ได้โดยส่งผ่านใน --log-level-display arg --log-level-display

ลองตอนนี้แล้วคุณจะเห็นข้อความบันทึก 'ฉันมีอุปกรณ์' ปรากฏขึ้นอีกครั้งใน stdout นอกเหนือจากการเข้าสู่ระบบไฟล์:

tf> run example/helloworld --log-level-display info
…
05-24 18:53:50 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

นั่นคือทั้งหมดที่ทุกคน!

โปรดทราบว่าหากคุณติดขัดกับสิ่งใดสิ่งหนึ่ง ซอร์สโค้ดของสหพันธ์การค้า มีข้อมูลที่เป็นประโยชน์มากมายที่ไม่ได้เปิดเผยในเอกสารประกอบ หากวิธีอื่นล้มเหลว ให้ลองถามบน Google Group ที่ใช้แพลตฟอร์ม Android โดยมี "สหพันธ์การค้า" อยู่ในหัวเรื่องข้อความ