Како направив да ми пристигаат Android нотификации на Apple Watch – приказна со многу компоненти

Ова е гостински текст на Горјан Јовановски првично објавен на неговиот блог на Medium. Горјан е креатор на апликацијата Мој Воздух (AirCare), активист за заштита на природата, TED-говорник и поранешeн Team Lead во Booking.com. Денес Горјан е ИТ консултант, тренер на успешни тимови и јавен говорник.


Го сакам мојот Android. Можеби го сакам и премногу, па кога купив iPhone и Apple Watch за мојот стартап да може да развива повеќе апликации на нив, не можев да се префрлам. Apple Watch е одличен, но навикнат на отвореноста на Android, па ми недостасуваат „power“-функционалности на iPhone.

Накратко, ми се допаѓа Apple Watch, но не ми се допаѓаше што не можам да го користам со Android.

Секако има неколку самостојни апликации, но добивањето на нотификации е функционалноста која ми е потребна. Мојата мисија беше да го остварам ова. И ми успеа! По три дена обиди и тестови, на мојот Apple часовник пристигаат нотификациите од мојот Pixel телефон. Сакате да дознаете како, или можеби самите да се обидете? Продолжете да читате!

Дисклејмер: За овој туторијал треба да познавате технологија, да знаете основи на развој на апликации, да имате Apple Developer Account и да можете да развивате на Xcode.

Потребни алатки

Потребно време: 1 час, ако сте запознаени со развој на апликации и сервер

Концепт

Идејата е едноставна: По WatchOS 6.0, може да развиваме самостојни апликации за Apple Watch за кои не е потребен iPhone за да работат.

  1. Ќе креираме Android апликација која ќе има Notification Listener Service, и за секоја нотификација ќе ги испраќаме title и body до нашиот PHP сервер.
  2. На нашиот сервер ќе креираме куса PHP скрипта која очекува повици од нашата Android апликација. Го користи името на пакетот од апликацијата што ја испраќа нотификацијата, за да ја пронајде нејзината слика од Play Store. Потоа со помош на Firebase SDK ќе ја препрати нотификацијата на одреден „topic“, т.е. канал, кој нашиот часовник го следи.
  3. Ќе креираме самостојна WatchOS апликација со интегриран Firebase Messaging, која слуша на специфични топици (topic) на кои нашиот сервер испраќа пораки. За ова да функционира часовникот треба да биде поврзан на WiFi/4G мрежа.

Штом некоја нотификација пристига на Android, го повикуваме серверот, кој го повикува Firebase, и оттука нашата порака пристига на WatchOS апликацијата.

Прв чекор: Android апликација 

Ако имате искуство со развој на апликации за Android ова ќе биде наједноставен дел од овој туторијал. Креирате basic activity, потоа креирате NotificationListenerService.

Внимавајте да ја замените baseURL променливата со линк до вашиот .php фајл на серверот откако ќе го креирате во чекор 2.

public class MyService extends NotificationListenerService{

    LinkedList<String> sentBodies;

    @Override
    public void onCreate() {
        super.onCreate();
        sentBodies = new LinkedList<>();
        System.out.println("********* SERVICE STARTED ***********");
    }

    //Use this function if you want the app's name
    public String getAppName(String packageName){
        final PackageManager pm = getApplicationContext().getPackageManager();
        ApplicationInfo ai;
        try {
            ai = pm.getApplicationInfo(packageName, 0);
        } catch (final PackageManager.NameNotFoundException e) {
            ai = null;
        }
        return (String) (ai != null ? pm.getApplicationLabel(ai) : "(unknown)");
    }

    public void sendNotification(String title, String body, String packageName) throws Exception{
        RequestQueue queue = Volley.newRequestQueue(this);
        title = URLEncoder.encode(title, "utf-8");
        body = URLEncoder.encode(body, "utf-8");
        String baseURL = ""; // <--- Insert URL to your server's script here
        String fullURL = baseURL + "?title="+title+"&body="+body+"&packageName="+packageName;

        StringRequest stringRequest = new StringRequest(Request.Method.GET, fullURL,
                response -> {Log.v("moj", "Success, server pinged.");}, error -> {Log.v("moj", "Failure, could not ping server: " + error.getMessage());});
        queue.add(stringRequest);

    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        Log.v("ANB","***** Notification received *****");
        String pack,title,text;
        Bundle extras;
        if(sbn.isOngoing()){
            return;
        }

        try {
            pack = sbn.getPackageName();
            extras = sbn.getNotification().extras;
            title = extras.getString("android.title");
            text = extras.getCharSequence("android.text").toString();

            String notificationTitle = title;
            String notificationBody = text;
            if(sentBodies.contains(notificationBody)){
                return;
            }
            sentBodies.add(notificationBody);
            sendNotification(notificationTitle, notificationBody, pack);

        }catch (Exception e)
        {
            Log.v("ANB","***** notification error *****");
            Log.v("ANB", Objects.requireNonNull(e.getMessage()));
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

Дефинирајте „listener“ во Android Manifest во <application> тагот  на следниот начин:

<!-- rest of manifest -->
<service android:name=".MyService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>
<!-- rest of manifest -->

Конечно стартувајте го сервисот во Main Activity.

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService(new Intent(MainActivity.this,MyService.class));

        //Optional, add a button to your layout that will open the Noficiation Access settings, where you can start or stop the listener
        Button startButton = (Button) findViewById(R.id.startButton);
        startButton.setOnClickListener(v -> {
            Intent intentS = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
            startActivity(intentS);
        });
    }
    
    
    //Optional, use this function to check if the app has access to notifications, thus the listener is running
    public boolean hasNotificationAccess(){
        ComponentName cn = new ComponentName(this, MyService.class);
        String flat = Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners");
        return flat != null && flat.contains(cn.flattenToString());
    }
}

Важни забелешки:

  • Не заборавајте да додадете дозвола за пристап на интернет во AndroidManifest.xml.
  • Не заборавајте да додадете Volley како зависност во build.gradle.
  • Пристапот до нотификации е специјална дозвола која треба да се овозможи „рачно“. Може да го направите преку „intent call“ на копчето кое го додадов во MainActivity, или рачно од нагодувањето на телефонот.
  • „Notification listener“ е специјален тип на сервис кој e стартуван и прекинат од системот, во зависност од пристапот на нотификации. Затоа единствен начин да ја запрете апликацијата да испраќа нотификации е да го прекинете пристапот до нотификации, или  да додадете дополнително конфигурирање на апликацијата кое сервисот може да го прочита и да постапи во согласност со тоа.

Со ова е завршен Android делот на туторијалот. Ако се е како што треба може да „изградите“ (build) и да ја стартувате апликацијата на Android телефон.

Чекор 2: Серверска страна

Сега да ја креираме PHP скриптата која ќе ги добива нотификациите од нашата Android апликација и ќе ги препраќа нотификациите преку Firebase до нашата Apple апликација.

Composer

Креирајте composer.json фајл и додадете ja Firebase PHP SDK зависноста во него, потоа со composer install инсталирајте го SDK во „vendor“ фолдерот.

{
    "require": {
        "kreait/firebase-php": "^5.9"
    }
}

Firebase account

Откако го инсталиравме Firebase SDK, време е да го регистрирате Firebase профилот. Штом ќе го креирате профилот (активирање на Analytics не е потребно), одберете ја „запчаник“-иконата веднаш до Project Overview, потоа одберете Settings и од Service Accounts одберете Generate new private key.

Преземете го JSON-фајлот, и зачувајте го на серверот во фолдер кој не е јавно достапен.

PHP код

На крај, ќе ја креираме watchNotification.php скриптата во истиот фолдер со „vendor“-фолдерот. Внимателно прочитајте ги коментарите во кодот, особено ако сакате да ја испратите и иконата на апликацијата која ја испраќа нотификацијата. Потоа треба да ја додадете и Simple DОМ библиотеката. Не заборавајте да ја промените патеката до вашиот Firebase Service Account JSON фајл во 57-от ред.

<?php
    require_once(__DIR__. "/vendor/autoload.php"); //Set correct path if needed
    //require_once(__DIR__. "/simple_html_dom/simple_html_dom.php"); //Optional, if you want the getImageURL function to work

    use Kreait\Firebase\Factory;
    use Kreait\Firebase\ServiceAccount;
    use Kreait\Firebase\Messaging\CloudMessage;
    use Kreait\Firebase\Messaging\Notification;
    use Kreait\Firebase;

    $useImage = false; //Change this to true to send an image of the app that sent the notification. If enabled, uncomment the second 'require_once' statement above and install the Simple DOM parser library

    // This function scrapes the HTML of the app's Play Store page and gets the image URL of it's icon.
    // This code is bound to break, so it will need to be updated when the design of the Play page changes.
    // In order not to SPAM the Play servers for every notification, create a 'packageIcons.json' file 
    // in the same folder as this script, add it read/write rights for the web user. The script will cache
    // all images of apps there, so the Play page for every app won't be scraped if it has already been before.
    function getImageURL($packageName){
        $json = json_decode(file_get_contents("packageIcons.json"));
        $link = null;
        if(isset($json->{$packageName})){
            $link = $json->{$packageName};
        }
        else{
            $html = file_get_html("https://play.google.com/store/apps/details?id=".$packageName);
            $bannerImage = $html->find('.xSyT2c img');
            $imgUrl = $bannerImage[0]->src;
            $json->{$packageName} = $imgUrl;
            file_put_contents("packageIcons.json", json_encode($json));
            $link = $imgUrl;
        }
        if(!is_null($link)){
            $link = str_replace("s180", "s360", $link); //Get a higher resolution of the image
        }
        return $link;
    }

    function getNotification($notificationTitle, $notificationBody, $imageURL){
        global $useImage;
        if($useImage && !is_null($imageURL)){
            return Notification::create($notificationTitle, $notificationBody, $imageURL);
        }
        return Notification::create($notificationTitle, $notificationBody);
    }

    function sendNotification($messaging, $notificationTitle, $notificationBody, $imageURL, $packageName){
        $message = CloudMessage::withTarget('topic', "watch")
            ->withAndroidConfig(['priority' => 'high', 'collapseKey' => 'anbNotifications'])
            ->withApnsConfig(['headers' => ['apns-collapse-id' => 'anbNotifications.' . $packageName]])
            ->withNotification(getNotification($notificationTitle, $notificationBody, $imageURL))
            ->withDefaultSounds();
    
        $messaging->send($message);
    }

    function sendPushNotifications($title, $body, $packageName){
        $firebase = (new Factory)->withServiceAccount('firebase.json'); // <-- Change this to the path to your Fireabse JSON Service Account file. Keep this file outside of the public access folders.
        $messaging = $firebase->createMessaging();
        $image = getImageURL($packageName);
        sendNotification($messaging, $title, $body, $image, $packageName);
    }

    if(isset($_GET['title']) && isset($_GET['body'])){
        sendPushNotifications($_GET['title'], $_GET['body'], $_GET['packageName']);
    }

Со ова завршивме два од трите дела потребни за ова да функционира. Уште сте тука? Ајде, речиси завршивме, остана уште последниот дел!

Чекор 3: WatchOS апликација

Време е за последниот дел. Ова е покомплексен дел, но можете да го завршите.

Ископирајте го кодот од примерот

Одличните луѓе во Google имаат креирана апликација пример која го прави токму она што нас ни е потребно. Во основа ќе го ископираме нивниот код од ова git repo.

Уредување на Podfile

Ќе забележите дека подфајлот од примерот користи локална зависност. Овие се постари, а нас ни требаат најновите. Затоа ќе го уредиме подфајлот да изгледа вака, и ќе ги додадеме точните имиња:

source 'https://cdn.cocoapods.org/'
use_frameworks!

target 'ANB WatchKit Extension' do #Make sure to put the right target name here, this is the EXTENSION target
  platform :watchos, '7.0'
  pod 'Firebase/Messaging'
end

target 'ServiceExtension' do
  platform :watchos, '7.0'
  pod 'Firebase/Messaging'
end

Потоа само извршете ја командата pod install и отворете го новиот .xcworkspaceфајл кој Pod го генерираше за нас. Отсега користете го овој фајл наместо .xcodeproj.

Регистрирај bundle IDs

Креирајте сопствен ID за секој „target“ во кодот кој го копирате од Google GitHub репозиториумот, внимавајќи да го замените com.google.firebase.extensions.dev со ваш Bundle.

Ако Xcode не го направи ова автоматски, регистрирајте ги сите „ID“-а од Apple Developer профилот и активирајте „Push“-нотификации https://developer.apple.com/account/resources/identifiers/list.

Регистрирајте ја апликацијата во Firebase

Сега со вашиот „bunde ID“ вратете се назад во Firebase профилот и додадете iOS апликација. Важно: регистрирајте ја апликација која завршува со .WatchKitApp, не другите bundle ID-ња што ги креиравме.

За време на регистрацијата ќе го добиете GoogleService-Info.plist фајлот. Преземете го фајлот и поставете го во root-фолдерот (локацијата каде што се наоѓа .xcworkspace) и проверете дали „target membership“ е селектиран за секој „target“.

Генерирајте и сетирајте APN-клуч

На Firebase му е потребен Apple Push Notification клуч за авторизација за да може да ги испраќа нотификациите на часовникот. Од девелопер конзолата генерирајте клуч за авторизација, селектирајте Apple Push Notifications service (APNs) како тип.

Преземете го клучот. На Firebase Project Setting страницата одберете Cloud Messaging и во iOS app configuration полето качете го APN-клучот.

Стартувајте  ја апликацијата

По ова поврзете ги вашиот телефон и часовник со Xcode и стартувајте ја апликацијата на вашиот часовник. Следете што се случува во известувањата и проверете дали Firebase може да го пронајде фајлот за конфигурација, дали се поврзува на сервисот, дали генерира токен и се „претплатува“ на „watch“. Ако се е во ред, „излезот“ ќе ги содржи овие податоци.

Тест нотификација

На Firebase конзолата, од листата линкови на левата страна пронајдете го Cloud Messaging и одберете Send your first message. Внесете наслов и содржина (body) на пораката, во следниот дел одберете „Topic“ и внесете „watch“.

На крај одберете Review и екранот ќе изгледа вака. Може да го занемарите предупредувањето, само значи дека не сме активирале аналитика за проектот.

Кога ќе ја објавите пораката, тест нотификацијата треба да пристигне на вашиот часовник. Сега само треба да провериме дека сите делови се активни и вашиот Android телефон ќе започне да испраќа нотификации до вашиот Apple Watch.

Финални забелешки

Бидејќи целата комуникација се прави преку интернет, часовникот мора да биде поврзан. Ако немате 4G часовник тогаш користете апликации како Automate за автоматски да креирате хотспот на Android кога излегувате од дома/канцеларија, за часовникот да може да се поврзе и да добива нотификации.

Ова беше луда авантура за да го поврземе Android да комуницира со нашиот Apple Watch, но може да се изведе. Најголем дел од проектот може да се поедностави и да се автоматизира, но се сомневам дека Apple ќе дозволи официјална апликација за ова. Затоа, барем засега мора чепкаме и да хакираме вака.

Ова секако може да работи и во обратната насока, од Apple Watch да се поврземе на Android телефон преку Firebase data-пораки (можеби одговор на нотификации преку часовник), но ова е проект за некоја следна прилика…

Стани премиум член и доби пристап до сите содржини, специјален попуст на над 2.200 производи во ИТ маркет, верификуван профил и можност за огласување на ИТ Огласник. Плус ќе го поддржиш медиумот кој го градиме цели 16 години!

basic

членство

42 ден./мес

зачлени се

1337

членство

125 ден./мес

зачлени се
* плаќањето е на годишно ниво

Доколку веќе имаш премиум членство, најави се тука.

Добивај известувања
Извести ме за
guest
1 Коментар
Најнови
Најстари Со највеќе гласови
Inline Feedbacks
View all comments
trackback

[…] post Како направив да ми пристигаат Android нотификации на Apple W… appeared first on […]

види ги сите огласи на kariera.it.mk