ลองเล่น Google Awareness API เมื่อเราสามารถเขียนแอปเพื่อเล่นกับบริบทของผู้ใช้ได้

Posted on 29 Jun 2016 23:17 | 18300 reads | 0 shares
 

ในงาน Google I/O 2016 ที่ผ่านมา กูเกิ้ลมีการประกาศเปิดตัว API ตัวใหม่ที่น่าสนใจมากนามว่า Awareness API อันนี้เป็นวีดีโอ Session ในงานความยาว 35 นาทีครับ

และมาวันนี้ กูเกิ้ลก็ปล่อย API ชุดนี้ให้เราได้ใช้งานกันแล้ว หลังจากลองเล่นแล้วก็ถือว่าน่าสนใจทีเดียวเลยเขียนเป็นบล็อกให้อ่านกันครับ

อะไรคือ Awareness API?

คำนิยามของ Awareness API ที่กูเกิ้ลให้ไว้คือ

A unified sensing platform enabling applications to be aware of multiple aspects of a users context, while managing battery and memory health.

ฟังดูงงๆยากๆแต่ความจริงมันเป็น API ที่ตรงไปตรงมาและง่ายมากคือ มันเป็น API ที่ชุดคำสั่งสามารถดึงข้อมูลที่อิงกับบริบท(Context)ของผู้ใช้ในขณะนั้นๆได้ หรือหากไม่เข้าใจคำว่าบริบท จริงๆมันก็คือการดึงข้อมูลที่อิงกับสถานการณ์รอบข้างของผู้ใช้คนนั้นๆนั่นเอง ยกตัวอย่างเช่น

- เวลาท้องถิ่นของตำแหน่งที่ผู้ใช้คนนั้นๆอยู่

- สถานที่ที่ผู้ใช้คนนั้นๆอยู่อุณหภูมิกี่องศา

- คนๆนั้นอยู่เฉยๆ เดินอยู่ วิ่งอยู่หรือขี่จักรยานอยู่

- ผู้ใช้เข้าใกล้พิกัดที่กำหนดรึเปล่า

- ตรวจสอบว่าหูฟังเสียบอยู่รึเปล่า

จริงๆ API หลายๆตัวของแอนดรอยด์ก็ทำได้อยู่แล้วเช่นอยากรู้อุณหภูมิก็ดึงพิกัดแล้วไปดึงอุณหภูมิจาก Weather API หรืออยากรู้ว่าคนๆนั้นอยู่เฉยๆหรือวิ่งอยู่เราก็สามารถดึงพิกัดแล้วคำนวณหาความเร็วของคนๆนั้นได้

แต่ก็จะเห็นว่ามันเป็นงานที่ค่อนข้างวุ่นวายและอาจเกิดความผิดพลาดได้ แต่ด้วย Awareness API จะทำให้การกระทำเหล่านี้ง่ายขึ้นแบบมหาศาลเหลือเพียงไม่กี่บรรทัด ความแม่นยำของข้อมูลที่ได้ก็สูงมากเพราะผ่านการประมวลผลแล้วไม่ใช่เป็นเพียงข้อมูลดิบๆแต่อย่างใด นอกจากนั้นปัญหาสามัญอย่างเรื่องการกินแบตเตอรี่ก็ไม่ใช่ปัญหาอีกต่อไปเพราะ Awareness API ทำการ Optimize ทุกอย่างมาดีแล้วจึงสามารถใช้งานได้อย่างไม่ต้องกังวลใดๆ

Awareness API เลยเป็นอีกหนึ่ง API สุดเทพที่ถึงจะดูเหมือนไม่มีอะไรเพราะมันเรียกใช้ง่ายมากและตรงไปตรงมา แต่ถ้าแอปของท่านมีการใช้ฟีเจอร์เหล่านี้หละก็ มันจะกลายเป็น API ที่มีประโยชน์มากเลยทีเดียว

ประเภทของบริบท(Context)ที่สามารถใช้งานได้

แล้วเราสามารถเล่นกับ Context อะไรของผู้ใช้ได้บ้าง? สำหรับ Awareness API เราสามารถเล่นได้ถึง 7 ชนิดด้วยกันได้แก่

Time - ดึงเวลาท้องถิ่นของผู้ใช้คนนั้นๆ

Location - ดึงพิกัด Lat/Long ที่ผู้ใช้อยู่

Place - เข้าถึงข้อมูลสถานที่รอบๆตัวของผู้ใช้

Activity - ดูสถานะการเคลื่อนที่ของผู้ใช้เช่น อยู่เฉยๆ เดินอยู่ วิ่งอยู่หรือว่าขี่จักรยานอยู่

Beacons - ดูว่ารอบตัวมี Beacon อะไรวางอยู่บ้าง

Headphones - ดูว่าผู้ใช้เสียบหูฟังไว้หรือไม่

Weather - เข้าถึงข้อมูลสภาพภูมิอากาศของพื้นที่ที่ผู้ใช้คนนั้นๆอยู่

ซึ่ง 7 อย่างนี้ก็เรียกว่าครอบคลุมงานที่เราใช้กันทั่วไปได้หมดแล้ว

API สองชุดของ Awareness API

ใน Awareness API มีกลุ่มของ APIs อยู่ทั้งหมดสองชุดด้วยกันได้แก่

Snapshot API - การ "ดึงข้อมูลตาม Context" ตามที่ลิสต์ไว้ด้านบนเอามาใช้งาน

Fence API - การ "ตรวจจับการเปลี่ยนแปลง Context" ของผู้ใช้ เช่น ถ้าผู้ใช้เสียบหูฟังหรือถ้าผู้ใช้เข้าใกล้พิกัดที่กำหนดให้เรียก BroadcastReceiver ที่กำหนด

จริงๆ API ทั้งสองชุดก็เข้าถึงข้อมูลชุดเดียวกันนั่นแหละ แต่ตัวนึงเป็นการดึงข้อมูลส่วนอีกตัวเป็นการจับการเปลี่ยนแปลง ทางกูเกิ้ลแยกไว้ให้สื่อสารง่ายขึ้นเท่านั้นเอง

Get Started

Awareness API ถูกบรรจุอยู่ใน Google Play Services 9.2 เป็นต้นไป การจะใช้งาน API เหล่านี้เราเลยต้องดึง GMS เข้ามาในโปรเจคด้วย เริ่มต้นจากการสร้างโปรเจคใน Google Developer Console ก่อน

1) สร้าง Project ขึ้นมาใน https://console.developers.google.com (หรือจะใช้โปรเจคตัวเดิมที่มีอยู่แล้วก็ได้)

2) เข้าหน้า API Manager และค้นหาคำว่า Awareness และกดที่ Awareness API

3) กด Enable

4) ไปที่หน้า Credentials แล้วกด Create credentials -> API key -> Android key ใส่ชื่อที่ต้องการ (เช่น Android Key) แล้วกด Create ได้เลย (หรือถ้ามี Android key ที่ถูกสร้างไว้อยู่แล้วก่อนหน้านี้ก็ข้ามขั้นตอนนี้แล้วใช้รหัสเก่าได้เลย)

5) จากนั้นจะได้ API key ที่หน้าตาประมาณว่า AIzaSyBdVl-cTICSwYKrZ95LoVuw7dbMuDt1KG0 ให้ก็อปเก็บไว้ เราจะใช้มันในโปรเจคแอนดรอยด์ครับ

6) เปิด Android SDK Manager แล้วอัปเดต Google Play Services และ Google Repository เป็นเวอร์ชันล่าสุด

7) สลับมาที่ Android Studio สร้างโปรเจคใหม่ขึ้นมาให้เรียบร้อยแล้วเปิดไฟล์ build.gradle ของโมดูลแอปที่ต้องการแล้วใส่ dependency ไปว่า

dependencies {
    compile 'com.google.android.gms:play-services-contextmanager:9.2.0'
}

แล้ว Sync Gradle ให้เรียบร้อย

7) เปิดไฟล์ AndroidManifest.xml แล้วเพิ่ม meta-data เข้าไปใน <application> tag ดังนี้

        <meta-data
            android:name="com.google.android.awareness.API_KEY"
            android:value="YOUR_KEY" />
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="YOUR_KEY" />
        <meta-data
            android:name="com.google.android.nearby.messages.API_KEY"
            android:value="YOUR_KEY" />

และอย่าลืมแทน YOUR_KEY ด้วย API key ที่ได้ด้านบนด้วย

โดยตัวแรกให้ใส่เสมอ ส่วนตัวที่สองเอาไว้ใช้ในการเข้าถึง Place (สถานที่) และตัวสุดท้ายเอาไว้เข้าถึง Beacon หากไม่ได้ใช้ตัวไหนสามารถเอาออกได้

8) เพิ่ม Permission ในไฟล์ AndroidManifest.xml ตัวเดิมไว้สองตัวภายใน <manifest> tag

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

โดยตัวบนเอาไว้เพื่อเข้าถึงพิกัด GPS และตัวล่างเอาไว้ตรวจจับการเคลื่อนไหวของผู้ใช้

9) ใน MainActivity.java ให้ทำการ Initialize GoogleApiClient เพื่อให้เรียกใช้ Google Play Services ได้ โดยใส่โค้ดไว้ใน onCreate ดังนี้

public class MainActivity extends AppCompatActivity {

    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mGoogleApiClient = new GoogleApiClient.Builder(MainActivity.this)
                .addApi(Awareness.API)
                .build();
        mGoogleApiClient.connect();
    }
}

เป็นอันเรียบร้อย โปรเจคของท่านพร้อมใช้งาน Awareness API แล้วครับ จากนี้จะเป็นการทดสอบการเรียกใช้ API แต่ละตัวซึ่งการรันคำสั่งเหล่านี้เครื่องที่ใช้ทดสอบจำเป็นต้องมี Google Play Services เวอร์ชั่น 9.2 เป็นต้นไป ให้อัปเดต Google Play Services บนเครื่องทดสอบหรืออีมูเลเตอร์เป็นตัวล่าสุดด้วยไม่งั้นจะรันไม่ได้ครับ

Snapshot API

เริ่มต้นจากสิ่งง่ายๆอย่างการ "ดึงข้อมูลมาดู" ก่อน ซึ่งในที่นี้ก็คือ Snapshot API นั่นเอง วางโครงโค้ดไว้ก่อนดังนี้

    private static final String TAG = "Awareness";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        initSnapshots();
    }

    private void initSnapshots() {

    }

จากนั้นโค้ดทั้งหมดจากนี้ให้ใส่ไว้ในคำสั่ง initSnapshots() นะครับ จะได้ทำงานทันทีตอนที่ Activity ถูกเปิดมา

โดยโค้ดในกลุ่ม Snapshot API จะเรียกผ่านคลาส Awareness.SnapshotApi กันครับ เริ่มเลยละกันนะ

Activity

เริ่มจากการทดสอบ "การเคลื่อนไหว" (Activity) ก่อนเลย นี่คือโค้ดครับ

        Awareness.SnapshotApi.getDetectedActivity(mGoogleApiClient)
                .setResultCallback(new ResultCallback<DetectedActivityResult>() {
                    @Override
                    public void onResult(@NonNull DetectedActivityResult detectedActivityResult) {
                        if (!detectedActivityResult.getStatus().isSuccess()) {
                            Log.e(TAG, "Could not get the current activity.");
                            return;
                        }
                        ActivityRecognitionResult ar = detectedActivityResult.getActivityRecognitionResult();
                        DetectedActivity probableActivity = ar.getMostProbableActivity();
                        Log.i(TAG, probableActivity.toString());
                    }
                });

ก็ค่อนข้างตรงไปตรงมามาก เรียก getDetectedActivity แล้วรอผลลัพธ์กลับมาทาง Callback แล้วก็เช็คว่าสำเร็จมั้ย ถ้าสำเร็จก็ดึงผลลัพธ์มาแสดงผล นี่คือตัวอย่างผลลัพธ์จากบรรทัด Log.i ครับ

I/Awareness: DetectedActivity [type=STILL, confidence=100]

ก็จะเห็นว่าเราสามารถเช็คได้ว่าผู้ใช้กำลังเคลื่อนไหวแบบไหนอยู่ โดยเราสามารถเช็คจากคำสั่ง probableActivity.getType() ซึ่งจะได้ผลกลับมาเป็น int ค่าตามนี้

    public static final int IN_VEHICLE = 0;
    public static final int ON_BICYCLE = 1;
    public static final int ON_FOOT = 2;
    public static final int STILL = 3;
    public static final int UNKNOWN = 4;
    public static final int TILTING = 5;
    public static final int WALKING = 7;
    public static final int RUNNING = 8;

นอกจากนั้นยังสามารถเช็คความมั่นใจของผลลัพธ์ได้จาก probableActivity.getConfidence() โดยค่าจะอยู่ในช่วง 0-100 ครับ

คำสั่งอื่นๆก็จะเรียกในรูปแบบเดียวกันนี้ งั้นไปตัวต่อไปเลยละกัน

Headphones

ตรวจเช็คสถานะการเสียบหูฟัง โค้ดตามนี้ครับ

        Awareness.SnapshotApi.getHeadphoneState(mGoogleApiClient)
                .setResultCallback(new ResultCallback<HeadphoneStateResult>() {
                    @Override
                    public void onResult(@NonNull HeadphoneStateResult headphoneStateResult) {
                        if (!headphoneStateResult.getStatus().isSuccess()) {
                            Log.e(TAG, "Could not get headphone state.");
                            return;
                        }
                        HeadphoneState headphoneState = headphoneStateResult.getHeadphoneState();
                        if (headphoneState.getState() == HeadphoneState.PLUGGED_IN) {
                            Log.i(TAG, "Headphones are plugged in.\n");
                        } else {
                            Log.i(TAG, "Headphones are NOT plugged in.\n");
                        }
                    }
                });

ทุกอย่างอยู่ในรูปแบบเดิมและเช็คผลลัพธ์ว่าเสียบหูฟังหรือไม่ผ่านคำสั่ง headphoneState.getState() โดยเป็นไปได้สองค่าด้วยกัน ได้แก่ HeadphoneState.PLUGGED_IN และ HeadphoneState.UNPLUGGED ส่วนความหมายก็ตรงตัวครับ

Location

ตรวจสอบพิกัด Lat/Long โค้ดตามนี้ครับ

            Awareness.SnapshotApi.getLocation(mGoogleApiClient)
                    .setResultCallback(new ResultCallback<LocationResult>() {
                        @Override
                        public void onResult(@NonNull LocationResult locationResult) {
                            if (!locationResult.getStatus().isSuccess()) {
                                Log.e(TAG, "Could not get location.");
                                return;
                            }
                            Location location = locationResult.getLocation();
                            Log.i(TAG, "Lat: " + location.getLatitude() + ", Lon: " + location.getLongitude());
                        }
                    });

สามารถดึงค่า Lat/Long จากคำสั่ง location.getLatitude() และ location.getLongitude() นอกจากนั้นยังสามารถดึงค่าอื่นๆเช่น Altitude และมีคำสั่งสำหรับคำนวณต่างๆอีกหลายตัว สามารถอ่านเพิ่มเติมได้จากเอกสารของคลาส Location ครับ

* สำหรับการเรียกใช้คำสั่ง Location บน Android M เป็นต้นไป เราจำเป็นต้องใส่โค้ด Runtime Permission เพื่อขออนุญาตผู้ใช้ในการเข้าถึง GPS ด้วย Permission ACCESS_FINE_LOCATION ก่อนด้วย โดยโค้ดการขอ Permission หน้าตาจะเป็นประมาณนี้

        if (ContextCompat.checkSelfPermission(
                MainActivity.this,
                android.Manifest.permission.ACCESS_FINE_LOCATION) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    MainActivity.this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    12345
            );
        }

ให้ Implement ให้เรียบร้อยมิฉะนั้นจะใช้งานไม่ได้ครับ สำหรับเรื่องของ Runtime Permission สามารถอ่านเพิ่มได้จากบล็อก สิ่งที่นักพัฒนาต้องรู้เกี่ยวกับระบบ Runtime Permission ใหม่ของแอนดรอยด์ รีบปรับโค้ดก่อนจะสายเกินไป

Places

อันนี้เป็นการขอสถานที่รอบตัวครับ ซึ่งการเข้าถึง Places เราจะต้องไป Enable Google Places for Android ใน Google Developer Console ก่อนให้เรียบร้อยด้วย จากนั้นโค้ดตัวอย่างนี้จะลิสต์ผลลัพธ์ 5 ที่แรกมาให้ (ถ้ามี)

            Awareness.SnapshotApi.getPlaces(mGoogleApiClient)
                    .setResultCallback(new ResultCallback<PlacesResult>() {
                        @Override
                        public void onResult(@NonNull PlacesResult placesResult) {
                            if (!placesResult.getStatus().isSuccess()) {
                                Log.e(TAG, "Could not get places.");
                                return;
                            }
                            List<PlaceLikelihood> placeLikelihoodList = placesResult.getPlaceLikelihoods();
                            // Show the top 5 possible location results.
                            if (placeLikelihoodList != null) {
                                for (int i = 0; i < 5 && i < placeLikelihoodList.size(); i++) {
                                    PlaceLikelihood p = placeLikelihoodList.get(i);
                                    Log.i(TAG, p.getPlace().getName().toString() + ", likelihood: " + p.getLikelihood());
                                }
                            } else {
                                Log.e(TAG, "Place is null.");
                            }
                        }
                    });

ซึ่งเราสามารถดึงรายละเอียดของสถานที่จาก p.getPlace() ซึ่งมีอยู่หลายอย่างเช่น getAddress() สำหรับการดึงที่อยู่ getPhoneNumber() สำหรับดึงเบอร์โทร getPlaceTypes() เพื่อดูว่าสถานที่นั้นๆเป็นสถานที่ประเภทไหน เป็นต้น สำหรับรายละเอียดเพิ่มเติมให้อ่านเอกสารของคลาส Place ได้ครับ

* สำหรับการดึงค่า Places บน Android M เป็นต้นไป เราจำเป็นต้องใส่โค้ด Runtime Permission เพื่อขออนุญาตผู้ใช้ในการเข้าถึง GPS ด้วย Permission ACCESS_FINE_LOCATION ก่อนด้วย สามารถดูโค้ดจากหัวข้อด้านบนได้

Weather

ดึงสภาพภูมิอากาศของสถานที่ที่ผู้ใช้อยู่ โค้ดตามนี้ครับ

            Awareness.SnapshotApi.getWeather(mGoogleApiClient)
                    .setResultCallback(new ResultCallback<WeatherResult>() {
                        @Override
                        public void onResult(@NonNull WeatherResult weatherResult) {
                            if (!weatherResult.getStatus().isSuccess()) {
                                Log.e(TAG, "Could not get weather.");
                                return;
                            }
                            Weather weather = weatherResult.getWeather();
                            Log.i(TAG, "Weather: " + weather);
                        }
                    });

ซึ่งเราสามารถดึงผลลัพธ์ของสภาพอากาศได้หลายอย่างได้แก่

getTemperature() เพื่อดึงค่าอุณหภูมิจริงออกมา

getFeelsLikeTemperature() เพื่อดึงค่าอุณหภูมิที่รู้สึกได้

getHumidity() ดึงค่าความชื้น

getDewPoint() ดึงอุณหภูมิที่ไอน้ำเริ่มควบแน่น

getConditions() ดูว่าสภาพอากาศโดยรวมเป็นอย่างไร เช่น Clear, Cloudy, Foggy เป็นต้น

สามารถอ่านเอกสารเพิ่มเติมโดยละเอียดจากคลาส Weather ได้ครับ

* สำหรับการดึงค่า Weathers บน Android M เป็นต้นไป เราจำเป็นต้องใส่โค้ด Runtime Permission เพื่อขออนุญาตผู้ใช้ในการเข้าถึง GPS ด้วย Permission ACCESS_FINE_LOCATION ก่อนด้วย สามารถดูโค้ดจากหัวข้อด้านบนได้

Beacon

ตรวจสอบสถานะของ Beacon ที่อยู่บริเวณนั้น โดยการเรียกใช้ API นี้เราจะต้องไป Enable Nearby Messages API ใน Google Developer Console ก่อนให้เรียบร้อย จากนั้นอย่างแรกที่ต้องทำคือประกาศรายละเอียดของ Beacon ทั้งหมดดังนี้

            List BEACON_TYPE_FILTERS = Arrays.asList(
                    BeaconState.TypeFilter.with(
                            "my.beacon.namespace",
                            "my-attachment-type"),
                    BeaconState.TypeFilter.with(
                            "my.other.namespace",
                            "my-attachment-type"));

จากนั้นก็ดึงสถานะผ่านคำสั่ง getBeaconState

            Awareness.SnapshotApi.getBeaconState(mGoogleApiClient, BEACON_TYPE_FILTERS)
                    .setResultCallback(new ResultCallback<BeaconStateResult>() {
                        @Override
                        public void onResult(@NonNull BeaconStateResult beaconStateResult) {
                            if (!beaconStateResult.getStatus().isSuccess()) {
                                Log.e(TAG, "Could not get beacon state.");
                                return;
                            }
                            BeaconState beaconState = beaconStateResult.getBeaconState();
                            // Get info from the BeaconState.
                        }
                    });

สามารถดูผล State ของ Beacon แต่ละตัวผ่านคำสั่ง beaconState.getBeaconInfo() ได้ทันที สามารถอ่านรายละเอียดของคลาส BeaconInfo ได้จาก BeaconState.BeaconInfo ครับ

* สำหรับการดึงค่า Places บน Android M เป็นต้นไป เราจำเป็นต้องใส่โค้ด Runtime Permission เพื่อขออนุญาตผู้ใช้ในการเข้าถึง GPS ด้วย Permission ACCESS_FINE_LOCATION ก่อนด้วย สามารถดูโค้ดจากหัวข้อด้านบนได้

Time

สำหรับเวลาไม่มีอยู่ใน Snapshot API เพราะสามารถเรียกผ่านคำสั่งพื้นฐานของแอนดรอยด์ได้

ก็เป็นอันเสร็จเรียบร้อยสำหรับ Snapshot API จะเห็นว่าทุกอย่างตรงไปตรงมาและเรียบง่ายแต่ก็มีประโยชน์มากครับ ลองเล่นดูได้

Fence API

สำหรับ Fence เป็นการเล่นคำมาจาก Geofencing ซึ่งเป็นการส่งสัญญาณบอกว่าแอปเราเมื่อผู้ใช้อยู่ภายในบริเวณที่ระบุ แต่ใน Fence API ทำได้มากกว่านั้นก็คือสามารถตรวจจับการเปลี่ยนแปลงสถานะของ Context ที่ Awareness API ใช้งานได้ เรามาลองกันเลยละกันนะ

ก่อนอื่นก็วางโครงก่อน โดยกลไกการรับสัญญาณกลับมาเมื่อมีการเปลี่ยนแปลงของสถานะเราจะรับด้วย BroadcastReceiver ผ่านทาง Intent ซึ่งถูกฝากไว้ใน PendingIntent ดังนั้นเราขอสร้าง BroadcastReceiver, Intent และ PendingIntent ไว้ดังนี้ครับ

public class MainActivity extends AppCompatActivity {

    private static final String FENCE_RECEIVER_ACTION = "FENCE_RECEIVE";

    private HeadphoneFenceBroadcastReceiver fenceReceiver;
    private PendingIntent mFencePendingIntent;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        fenceReceiver = new HeadphoneFenceBroadcastReceiver();
        Intent intent = new Intent(FENCE_RECEIVER_ACTION);
        mFencePendingIntent = PendingIntent.getBroadcast(MainActivity.this,
                10001,
                intent,
                0);
    }

    private void registerFences() {
        // Create a fence.
    }

    private void unregisterFence() {
    }

    @Override
    protected void onStart() {
        super.onStart();
        registerFences();
        registerReceiver(fenceReceiver, new IntentFilter(FENCE_RECEIVER_ACTION));
    }

    @Override
    protected void onStop() {
        super.onStop();
        unregisterFences();
        unregisterReceiver(fenceReceiver);
    }

    class HeadphoneFenceBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

        }

    }

จากนั้นโค้ดส่วนการขอรับสถานะการเปลี่ยนแปลงเราจะใส่ไว้ใน registerFences() และการยกเลิกจะใส่ไว้ใน unregisterFences() กันครับ

ตรวจจับการเปลี่ยนแปลงสถานะการเสียบหูฟัง

เราจะขอทดสอบกับตัวอย่างที่ง่ายที่สุดถ้างั้นขอเป็นเรื่องการตรวจสอบการเปลี่ยนแปลง "สถานะการเสียบหูฟัง" (Headphone State) ละกันนะครับ ให้ใส่โค้ดใน registerFences() ไว้ดังนี้ เริ่มต้นจากการประกาศ AwarenessFence เพื่อบอกว่าเราต้องการจะตรวจจับอะไร ยกตัวอย่างเช่นหากเราต้องการตรวจจับการเปลี่ยนสถานะการเสียบหูฟังให้สร้างผ่านคลาส HeadphoneFence ดังนี้

        AwarenessFence headphoneFence = HeadphoneFence.during(HeadphoneState.PLUGGED_IN);

จากนั้นให้สั่ง Awareness.FenceApi.updateFences(...) เพื่อร้องขอการรับการเปลี่ยนแปลงสถานะดังนี้

        Awareness.FenceApi.updateFences(
                mGoogleApiClient,
                new FenceUpdateRequest.Builder()
                        .addFence("headphoneFenceKey", headphoneFence, mFencePendingIntent)
                        .build())
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(@NonNull Status status) {
                        if (status.isSuccess()) {
                            Log.i(TAG, "Fence was successfully registered.");
                        } else {
                            Log.e(TAG, "Fence could not be registered: " + status);
                        }
                    }
                });

โดยบรรทัดสำคัญคือ .addFence(...) หากมีการเปลี่ยนแปลงตาม AwarenessFence ที่กำหนด (ในที่นี้คือ headphoneFence) ตัว PendingIntent ที่กำหนด (ในที่นี้คือ mFencePendingIntent) จะถูกยิงออกมาและ BroadcastReceiver (ในที่นี้คือ fenceReceiver) ที่ผูกไว้ก็จะถูกเรียก

และเมื่อมีการ register ก็ต้องมีการ unregister เพื่อไม่ให้เกิด Memory Leak ดังนั้นในคำสั่ง unregisterFences() ให้ใส่โค้ดไว้ดังนี้

    private void unregisterFences() {
        Awareness.FenceApi.updateFences(
                mGoogleApiClient,
                new FenceUpdateRequest.Builder()
                        .removeFence("headphoneFenceKey")
                        .build()).setResultCallback(new ResultCallbacks<Status>() {
            @Override
            public void onSuccess(@NonNull Status status) {
                Log.i(TAG, "Fence " + "headphoneFenceKey" + " successfully removed.");
            }

            @Override
            public void onFailure(@NonNull Status status) {
                Log.i(TAG, "Fence " + "headphoneFenceKey" + " could NOT be removed.");
            }
        });
    }

เมื่อการ register/unregister เสร็จแล้ว ต่อไปเรามา Handle ที่ HeadphoneFenceBroadcastReceiver กันต่อ เพิ่มโค้ดในส่วน onReceive ตามนี้ครับ

    class HeadphoneFenceBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            FenceState fenceState = FenceState.extract(intent);

            Log.d(TAG, "Fence Receiver Received");

            if (TextUtils.equals(fenceState.getFenceKey(), "headphoneFenceKey")) {
                switch (fenceState.getCurrentState()) {
                    case FenceState.TRUE:
                        Log.i(TAG, "Fence > Headphones are plugged in.");
                        break;
                    case FenceState.FALSE:
                        Log.i(TAG, "Fence > Headphones are NOT plugged in.");
                        break;
                    case FenceState.UNKNOWN:
                        Log.i(TAG, "Fence > The headphone fence is in an unknown state.");
                        break;
                }
            }
        }
        
    }

หลักการทำงานค่อนข้างตรงไปตรงมา เมื่อมีการเปลี่ยนแปลงตาม Fence ที่กำหนด onReceive จะถูกเรียกและเราสามารถดึงผลลัพธ์จากคำสั่ง

FenceState fenceState = FenceState.extract(intent);

และจากนั้นก็เช็ค Key ว่าเป็นการเปลี่ยนแปลงสถานะของอะไรด้วยคำสั่ง

if (TextUtils.equals(fenceState.getFenceKey(), "headphoneFenceKey")) {

โดย Key ที่ว่านี้เป็น Key ที่เรากำหนดไว้ตรงคำสั่ง addFence นั่นเอง

จากนั้นก็เช็คเงื่อนไขจาก fenceState.getFenceState() ว่าเงื่อนไขที่กำหนดเป็น TRUE หรือเปล่าตามโค้ดด้านบนนั่นเอง

ทดสอบ

ตอนนี้โค้ดด้านบนสามารถใช้งานได้แล้ว ลองกดรันแล้วดูผลการทำงานได้เลย โดยอย่างแรกให้ลองเสียบหูฟังเข้าไปในช่องหูฟัง ในช่อง Logcat จะขึ้นดังนี้

D/Awareness: Fence Receiver Received
I/Awareness: Fence > Headphones are plugged in.

และถ้าถอดหูฟังก็จะขึ้นดังนี้

D/Awareness: Fence Receiver Received
I/Awareness: Fence > Headphones are NOT plugged in.

เรียบร้อยครับ ใช้งานได้แล้ว ง่ายใช่ม้า =D

Fence อื่นๆ

เราสามารถตรวจสอบสถานะของ Context ได้ 5 อย่าง โดยใช้คลาสดังต่อไปนี้

DetectedActivityFence - ตรวจจับการเปลี่ยนแปลงรูปแบบการเคลื่อนไหวของผู้ใช้

HeadphoneFence - ตรวจจับการเปลี่ยนแปลงสถานะการเสียบหูฟัง

TimeFence - ตรวจจับเวลาเมื่ออยู่ในช่วงที่กำหนด

LocationFence - ตรวจจับว่าผู้ใช้เข้าหรือออกพิกัดที่กำหนด

BeaconFence - ตรวจจับสถานะการเจอหรือไม่เจอ Beacon ที่กำหนด

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

ส่วน Weather กับ Place ไม่มี Fence API ให้ใช้ครับผม (เพราะไม่รู้ว่าจะเปลี่ยนแปลงอะไรนั่นเอง)

การรับเงื่อนไขหลายๆ Fence ร่วมกัน

ในหลายๆกรณีเราต้องการสร้างเงื่อนไข Fence หลายๆอย่างให้ทำงานร่วมกันเช่น

- ถ้าผู้ใช้วิ่งและเสียบหูฟังอยู่

- ถ้าผู้ใช้เดินเข้าพื้นที่ที่กำหนดและเป็นเวลาช่วงบ่าย

ตรงนี้ทำได้ไม่ยากเพราะเราสามารถสร้าง AwarenessFence หลายๆตัวขึ้นมา เช่น

        AwarenessFence headphoneFence = HeadphoneFence.during(HeadphoneState.PLUGGED_IN);
        AwarenessFence detectedActivityFence = DetectedActivityFence.during(DetectedActivityFence.RUNNING);

และทำการ And กันด้วยคำสั่ง AwarenessFence.and ดังนี้

        AwarenessFence andFence = AwarenessFence.and(headphoneFence, detectedActivityFence);

แล้วตอนสั่ง updateFences ให้โยน andFence ตัวนี้เข้าไปแทนก็เป็นอันเรียบร้อยครับ

ซึ่งนอกจาก and แล้วก็ยังมี AwarenessFence.or และ AwarenessFence.not ให้ใช้งานอีกด้วย สามารถประยุกต์ใช้ตามลอจิคที่ต้องการได้เลยครับ

Use Case

มีแอปบางตัวเริ่มเอา Awareness API ไปใช้จริงแล้ว ยกตัวอย่างเช่น

Superplayer Music ที่สามารถแนะนำเพลงจากสภาพภูมิอากาศได้ (ฟังเพลงเศร้าๆตอนฝนตกมันจะอินใช่ม้า)

Trulia แอปของเว็บขายบ้านที่สามารถเด้ง Notification บอกผู้ใช้ได้ทันทีที่ผู้ใช้เดินเข้าใกล้บริเวณบ้านที่มีการประกาศขายและอากาศดีพอจะแวะดูได้

ก็ลองประยุกต์ใช้ในแอปของท่านได้ครับ เมื่อเล่นกับบริบทของผู้ใช้ได้แอปจะเท่ขึ้นเยอะมาก

สรุป

ก็เป็นอันเรียบร้อย Awareness API ถือว่าเป็น API ที่มีประโยชน์มากต่อการเพิ่มฟีเจอร์เจ๋งๆให้แอปเราด้วยโค้ดเพียงไม่กี่บรรทัด ข้อจำกัดเดียวก็คือ API จะรันบนเครื่องที่มี Google Play Services เท่านั้น เครื่องจีนจะทำงานไม่ได้ ซึ่งถ้าไม่แคร์อะไรก็สามารถใช้งานได้ครับ

ก็หวังว่าบล็อกนี้จะมีประโยชน์ครับ ขอให้มีความสุขกับการเล่นกับ Context ของผู้ใช้จ้า =D

ผู้เขียน: nuuneoi (Android GDE, CTO & CEO at The Cheese Factory)
นักพัฒนาแบบ Full-Stack ที่มีประสบการณ์ในการพัฒนาแอพฯแอนดรอยด์มากว่า 6 ปีและอยู่ในวงการพัฒนาแอพฯมือถือมากว่า 12 ปี มีความสนใจทางด้าน Infrastucture, Service Side, Design, UI&UX, Hardware, Optimization, Cooking, Photographing, Blogging, Training, Public Speaking และรักที่จะแชร์เรื่องราวให้ผู้คนได้อ่านได้ฟังกันผ่าน Blog