다음을 통해 공유


방법 가이드: Graph 알림과 통합(Android)

Graph 알림을 사용하면 앱이 여러 디바이스에서 사용자를 대상으로 하는 알림을 보내고 관리할 수 있습니다.

Android에 프로젝트 로마 클라이언트 쪽 SDK를 사용하면, 로그인된 사용자를 대상으로 하는 앱 서버에서 게시된 알림을 Android 앱이 수신하도록 등록할 수 있습니다. SDK를 사용하면 앱 클라이언트가 새로 들어오는 알림 페이로드를 수신하고, 기존 알림의 상태를 관리하고, 알림 기록을 검색할 수 있습니다. 알림의 기본 사항 및 이를 통해 사용자 중심 알림을 전달하는 방법에 대한 자세한 내용은 Microsoft Graph 알림 개요를 참조하세요.

Graph 알림 등을 비롯한 프로젝트 로마 SDK의 모든 기능은 연결된 디바이스 플랫폼이라는 기본 플랫폼을 기반으로 구축됩니다. 이 가이드는 연결된 디바이스 플랫폼을 시작하는 데 필요한 단계를 안내하고 SDK에서 API를 사용하여 Graph 알림 관련 기능을 구현하는 방법을 설명하도록 설계되었습니다.

알림 시나리오와 관련된 참조 문서에 대한 링크는 API 참조 페이지를 참조하세요.

아래 단계에서는 프로젝트 로마 Android 샘플 앱의 코드를 참조합니다.

연결된 모든 디바이스 기능의 경우 지원되는 아키텍처(armeabi-v7a, arm64-v8a, x86 또는 x86_64) 중 하나 또는 에뮬레이터가 있는 Android 앱 개발 IDE 및 Android 디바이스가 필요합니다. 시스템에서 Android 4.4.2 이상을 실행해야 합니다.

연결된 디바이스 플랫폼 및 알림을 위한 사전 설정

원격 연결을 구현하기 전에 Android 앱이 원격 디바이스에 연결할 수 있을 뿐 아니라 알림을 보내고 받을 수 있도록 몇 가지 단계를 수행해야 합니다.

앱 등록

근거리 공유 API를 제외하고 프로젝트 로마 SDK의 거의 모든 기능에는 MSA(Microsoft Account) 또는 AAD(Azure Active Directory) 인증이 필요합니다. MSA가 아직 없는 경우 사용하려면 account.microsoft.com에 등록하세요.

참고

AAD(Azure Active Directory) 계정은 디바이스 릴레이 API에서 지원되지 않습니다.

선택한 인증 방법을 사용하여 애플리케이션 등록 포털의 지침에 따라 Microsoft에 앱을 등록해야 합니다. Microsoft 개발자 계정이 없는 경우 새로 만들어야 합니다.

MSA를 사용하여 앱을 등록하는 경우 클라이언트 ID 문자열을 받아야 합니다. 나중에 사용할 수 있도록 저장합니다. 이렇게 하면 앱이 Microsoft의 연결된 디바이스 플랫폼 리소스에 액세스할 수 있습니다. AAD를 사용하는 경우 Azure Active Directory 인증 라이브러리에서 클라이언트 ID 문자열을 가져오는 방법에 대한 지침을 참조하세요.

SDK 추가

다음 리포지토리 참조를 프로젝트의 루트에 있는 build.gradle 파일에 삽입합니다.

allprojects {
    repositories {
        jcenter()
    }
}

그런 다음, 다음 종속성을 프로젝트 폴더에 있는 build.gradle 파일에 삽입합니다.

dependencies { 
    ...
    implementation 'com.microsoft.connecteddevices:connecteddevices-sdk:+'
}

프로젝트의 AndroidManifest.xml 파일에서 <manifest> 요소 내에 다음 사용 권한을 추가합니다(아직 존재하지 않는 경우). 이렇게 하면 앱에서 인터넷에 연결하고 디바이스에서 Bluetooth 검색을 활성화하는 권한이 부여됩니다.

Bluetooth 관련 사용 권한은 Bluetooth 검색 사용에만 필요하며, 연결된 디바이스 플랫폼의 다른 기능에는 필요하지 않습니다. 또한 ACCESS_COARSE_LOCATION은 Android SDK 21 이상에서만 필요합니다. Android SDK 23 이상에서 개발자가 런타임 시 위치 접근을 허용하도록 사용자에게 요청해야 합니다.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

다음으로, 연결된 디바이스 기능을 사용할 활동 클래스로 이동합니다. 다음 패키지를 가져옵니다.

import com.microsoft.connecteddevices;
import com.microsoft.connecteddevices.remotesystems;
import com.microsoft.connecteddevices.remotesystems.commanding;

인증 및 계정 관리 설정

연결된 디바이스 플랫폼에는 등록 프로세스에서 사용할 유효한 OAuth 토큰이 필요합니다. OAuth 토큰을 생성하고 관리하는 데 원하는 방법을 사용할 수 있습니다. 그러나 개발자가 플랫폼을 쉽게 사용할 수 있도록 인증 공급 업체의 편의를 위해 새로 고침 토큰을 생성하고 관리하는 데 사용할 수 있는 iOS 샘플 앱의 일부로 포함시켰습니다.

ConnectedDevicesAccountManager 인터페이스를 직접 구현하려면, 다음 정보를 확인해 두십시오.

MSA를 사용하는 경우 다음과 같은 범위를 로그인 요청에 포함시켜야 합니다. "wl.offline_access", "ccs.ReadWrite", "dds.read", "dds.register", "wns.connect", "asimovrome.telemetry""https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp".

AAD 계정을 사용하는 경우 다음 대상을 요청해야 합니다. "https://cdpcs.access.microsoft.com", "https://cs.dds.microsoft.com", "https://wns.windows.com/""https://activity.microsoft.com".

참고

AAD(Azure Active Directory) 계정은 디바이스 릴레이 API에서 지원되지 않습니다.

제공된 ConnectedDevicesAccountManager 구현을 사용하든 사용하지 않든, AAD를 사용하는 경우 Azure Portal(portal.azure.com > Azure Active Directory > 앱 등록)의 앱 등록에서 다음 사용 권한을 지정해야 합니다.

  • Microsoft 활동 피드 서비스
    • 이 앱에 대한 사용자 알림 전송 및 수정
    • 사용자의 활동 피드에 앱 활동 읽기 및 쓰기
  • Windows 알림 서비스
    • Windows 알림 서비스에 디바이스 연결
  • Microsoft 디바이스 디렉터리 서비스
    • 디바이스 목록 보기
    • 디바이스 및 앱 목록에 추가
  • Microsoft 명령 서비스
    • 사용자 디바이스와 통신
    • 사용자 디바이스 읽기

푸시 알림에 대해 앱 등록

Firebase 클라우드 메시징 지원을 위해 애플리케이션을 Google에 등록합니다. 보낸 사람 ID 및 받은 서버 키는 나중에 사용할 수 있도록 기록해 두어야 합니다.

등록이 되면 푸시 알림 기능을 앱의 연결된 디바이스 플랫폼과 연결해야 합니다.

mNotificationRegistration = new ConnectedDevicesNotificationRegistration();
mNotificationRegistration.setType(ConnectedDevicesNotificationType.FCM);
mNotificationRegistration.setToken(token);
mNotificationRegistration.setAppId(Secrets.FCM_SENDER_ID);
mNotificationRegistration.setAppDisplayName("SampleApp");

디바이스 간 환경을 위해 Microsoft Windows 개발자 센터에 앱 등록

중요

이 단계는 프로젝트 로마 기능을 사용하여 Windows 외 디바이스를 요청하거나 데이터에 액세스하려는 경우에만 필요합니다. Windows 디바이스만을 대상으로 하는 경우에는 이 단계를 완료할 필요가 없습니다.

아래와 같이, 개발자 센터 대시보드로 이동하여 왼쪽 탐색 창의 디바이스 간 환경으로 이동하고 새 디바이스 간 앱 구성을 선택합니다. 개발자 센터 대시보드 – 디바이스 간 환경

개발자 센터 온보딩 프로세스에는 다음과 같은 단계가 필요합니다.

  • 지원되는 플랫폼 선택 - 앱이 존재하고 디바이스 간 환경에서 사용할 수 있는 플랫폼을 선택합니다. Graph 알림 통합의 경우, Windows, Android 및/또는 iOS 중에서 선택할 수 있습니다. 디바이스 간 환경 – 지원되는 플랫폼

  • 앱 ID 제공 – 앱이 있는 각 플랫폼의 앱 ID를 제공합니다. Android 앱의 경우, 프로젝트를 만들 때 앱에 할당한 패키지 이름입니다. 패키지 이름은 프로젝트 개요 -> 일반 아래에 있는 Firebase 콘솔에서 찾을 수 있습니다. 플랫폼당 다른 ID(최대 10개)를 추가할 수 있습니다. 동일한 앱 또는 다른 앱의 버전이 여러 개인 경우, 동일한 사용자를 대상으로 하는 앱 서버에서 보낸 동일한 알림을 수신할 수 있도록 하려는 경우입니다. 디바이스 간 환경 – 앱 ID

  • MSA 및/또는 AAD 앱 등록에서 앱 ID를 선택하거나 제공합니다. MSA 또는 AAD 앱 등록에 해당하는 이러한 클라이언트 ID는 위의 이전 MSA/AAD 앱 등록 단계에서 확보했습니다. 디바이스 간 환경 – MSA 및 AAD 앱 등록

  • Graph 알림 및 다른 연결된 디바이스 플랫폼 기능은 주요 플랫폼의 기본 알림 플랫폼을 활용하여 앱 클라이언트 엔드포인트 즉, WNS(Windows UWP), FCM(Android) 및 APNS(iOS)에 알림을 보냅니다. 이러한 알림 플랫폼에 대한 자격 증명을 제공하여 사용자를 대상으로 하는 알림을 게시할 때 Graph 알림이 앱 서버에 대한 알림을 전달할 수 있도록 설정합니다. Android의 경우 Microsoft Graph 알림을 사용하려면 클라우드 메시징 서비스를 사용하도록 설정하는 것이 필수입니다. 또한 필수 보낸 사람 ID는 Firebase 클라우드 메시징 보낸 사람 ID에 해당하며 API 키는 레거시 서버 키에 해당함을 유의하세요. 아래 스크린샷에 나온 것처럼 두 가지 모두 클라우드 메시징 탭의 Firebase 콘솔 -> 프로젝트 -> 설정에서 찾을 수 있습니다. 디바이스 간 환경 – 푸시 자격 증명

  • 마지막 단계는 등록한 앱에 대해 디바이스 간 앱 ID와 같은 역할을 하는 도메인에 대한 소유권이 앱에 있다는 것을 확인하는 검증 프로세스인, 디바이스 간 앱 도메인을 확인하는 것입니다. 디바이스 간 환경 - 도메인 확인

Graph 알림 채널 초기화

프로젝트 로마 SDK를 사용하면 Graph 알림, 사용자 활동 등을 비롯한 다양한 형식의 사용자 데이터를 수신하고 관리하기 위해 앱에서 다양한 채널을 구독할 수 있습니다. 이러한 데이터는 모두 UserDataFeed에 저장되고 동기화됩니다. UserNotification은 Graph 알림을 통해 보낸 사용자 대상 알림에 해당하는 클래스 및 데이터 형식입니다. Graph 알림과 통합하고 앱 서버에서 게시한 UserNotification을 받기 시작하려면 먼저 UserNotificationChannel을 만들어 사용자 데이터 피드를 초기화해야 합니다. 이 작업은 위의 플랫폼 초기화 단계와 동일한 방법으로 처리해야 합니다. 앱이 포그라운드로 올 때마다(플랫폼 초기화 이전이 아님) 다시 확인하고 가능한 경우 다시 수행해야 합니다.

다음 메서드는 UserNotificationChannel을 초기화합니다.

private UserNotificationChannel mNotificationChannel;
private UserDataFeed mUserDataFeed;

// ...

/**
 * Initializes the UserNotificationFeed.
 */
public void initializeUserNotificationFeed() {

    // define what scope of data this app needs
    SyncScope[] scopes = { UserNotificationChannel.getSyncScope() };

    // Get a reference to the UserDataFeed. This method is defined below
    mUserDataFeed = getUserDataFeed(scopes, new EventListener<UserDataFeed, Void>() {
        @Override
        public void onEvent(UserDataFeed userDataFeed, Void aVoid) {
            if (userDataFeed.getSyncStatus() == UserDataSyncStatus.SYNCHRONIZED) {
                // log synchronized.
            } else {
                // log synchronization not completed.
            }
        }
    });

    // this method is defined below
    mNotificationChannel = getUserNotificationChannel();
}

// instantiate the UserDataFeed
private UserDataFeed getUserDataFeed(SyncScope[] scopes, EventListener<UserDataFeed, Void> listener) {
    UserAccount[] accounts = AccountProviderBroker.getSignInHelper().getUserAccounts();
    if (accounts.length <= 0) {
        // notify the user that sign-in is required
        return null;
    }

    // use the initialized Platform instance, along with the cross-device app ID.
    UserDataFeed feed = UserDataFeed.getForAccount(accounts[0], PlatformBroker.getPlatform(), Secrets.APP_HOST_NAME);
    feed.addSyncStatusChangedListener(listener);
    feed.addSyncScopes(scopes);
    // sync data with the server
    feed.startSync();
    return feed;
}

// use the UserDataFeed reference to create a UserActivityChannel
@Nullable
private UserNotificationChannel getUserNotificationChannel() {
    UserNotificationChannel channel = null;
    try {
        // create a UserNotificationChannel for the signed in account
        channel = new UserNotificationChannel(mUserDataFeed);
    } catch (Exception e) {
        e.printStackTrace();
        // handle exception
    }
    return channel;
}

이 때, mNotificationChannelUserNotificationChannel 참조가 있어야 합니다.

UserNotificationReader를 만들어 들어오는 UserNotification을 받고 UserNotification 기록에 액세스

앞에서 본 것처럼, 앱 클라이언트에 도착하는 초기 Google Cloud Messaging 알림에는 숄더 탭만 포함되어 있으며, 이 숄더 탭 페이로드를 연결된 디바이스 플랫폼에 전달해야만 SDK를 트리거하여 연결된 디바이스 서버와 전체 동기화를 수행할 수 있습니다. 여기에는 앱 서버에서 게시한 모든 UserNotification이 포함되어 있습니다. 그러면 숄더 탭에 해당하는 앱 서버에서 게시한 전체 알림 페이로드를 가져오게 됩니다. (그리고 이전에 알림이 게시되었지만 디바이스 연결 또는 기타 문제로 인해 앱 클라이언트에 수신되지 않은 경우, 이러한 알림도 함께 가져오게 됩니다.) 이러한 실시간 동기화가 SDK에서 지속적으로 수행되기 때문에 로그인된 사용자의 UserNotification 데이터 피드의 로컬 캐시에 앱 클라이언트가 액세스할 수 있습니다. 이 경우 UserNotificationReader를 사용하면 앱 클라이언트에서 이 데이터 피드에 액세스할 수 있습니다. 그러면 이벤트 수신기를 통해 최신 알림 페이로드를 받거나 사용자의 알림 기록 보기 모델로 사용할 수 있는 전체 UserNotification 컬렉션에 액세스할 수 있습니다.

UserNotifications 받기

우선 UserNotificationReader를 인스턴스화하고 reader에 이미 있는 기존의 모든 UserNotification(사용하도록 설정하려는 환경에서 해당 정보를 사용하도록 하려면)을 가져와야 합니다. 특정 디바이스 엔드포인트가 사용자가 앱을 설치한 유일한 엔드포인트나 첫 번째 엔드포인트가 아닐 수도 있으니 앱 서버가 로그인된 사용자에게 알림을 이미 게시했다고 항상 가정하는 것이 안전합니다.

private static UserNotificationReader mReader;
private static final ArrayList<UserNotification> mHistoricalNotifications = new ArrayList<>();
// Instantiate UserNotificationReader
UserNotificationReaderOptions options = new UserNotificationReaderOptions();
mReader = mNotificationChannel.createReaderWithOptions(options);
// Read any previously published UserNotifications that have not expired yet
mReader.readBatchAsync(Long.MAX_VALUE).thenAccept(new AsyncOperation.ResultConsumer<UserNotification[]>() {
    @Override
    public void accept(UserNotification[] userNotifications) throws Throwable {
        synchronized (mHistoricalNotifications) {
            for (UserNotification notification : userNotifications) {
                if (notification.getReadState() == UserNotificationReadState.UNREAD) {
                    mHistoricalNotifications.add(notification);
                }
            }
        }
 
        if (RunnableManager.getHistoryUpdated() != null) {
            activity.runOnUiThread(RunnableManager.getHistoryUpdated());
        }
    }
});

이제 연결된 디바이스 플랫폼에서 동기화가 완료되고 알려줄 만한 새로운 변경 내용이 있을 때 트리거되는 이벤트 수신기를 추가합니다. Graph 알림의 경우, 새로운 변경 내용은 앱 서버에서 게시한 새로 받은 UserNotification이거나 서버 또는 동일한 사용자가 로그인한 다른 등록된 엔드포인트에서 발생한 UserNotifcation 업데이트, 삭제 및 만료일 수 있습니다.

이 이벤트 수신기는 기본 비즈니스 논리를 처리하고 시나리오에 따라 알림 페이로드의 콘텐츠를 "소비"하는 곳입니다. 현재 Google Cloud Messaging의 데이터 메시지를 사용하여 OS 수준 알림 트레이에서 시각적 알림을 생성하는 경우 또는 알림의 콘텐츠를 사용하여 앱 내 UI를 업데이트하는 경우, 해당 작업을 수행할 수 있습니다.

mReader.addDataChangedListener(new EventListener<UserNotificationReader, Void>() {
    @Override
    public void onEvent(UserNotificationReader userNotificationReader, Void aVoid) {
        userNotificationReader.readBatchAsync(Long.MAX_VALUE).thenAccept(new AsyncOperation.ResultConsumer<UserNotification[]>() {
        @Override
        public void accept(UserNotification[] userNotifications) throws Throwable {
            boolean updatedNew = false;
            boolean updatedHistorical = false;
            synchronized (sHistoricalNotifications) {
                for (final UserNotification notification : userNotifications) {
                    if (notification.getStatus() == UserNotificationStatus.ACTIVE && notification.getReadState() == UserNotificationReadState.UNREAD) {
                        switch (notification.getUserActionState()) {
                            case NO_INTERACTION:
                                // Brand new notification
                                // Insert business logic to construct a new visual notification in Android notification tray for the user to see
                                // ...
                            case DISMISSED:
                                // Existing notification that is marked as dismissed
                                // An app client receive this type of changes because another app client logged in by the same user has marked the notification as dismissed and the change is fanned-out to everywhere
                                // This state sync across app clients on different devices enable universal dismiss of notifications and other scenarios across multiple devices owned by the same user
                                // Insert business logic to dismiss the corresponding visual notification inside Android system notification tray, to make sure users don’t have to deal with redundant information across devices, and potentially insert this notification in your app’s notification history view
                                // ...
                            default:
                                // Unexpected
                        }
                    } else {
                        // ...
                    }
                }
            }
        }
    });
}
});

기존 UserNotification 상태 업데이트

이전 섹션에서는 reader를 통해 받은 UserNotification 변경 내용이 기존 UserNotification에 대한 상태 업데이트, 즉 해제되었거나 읽힌 것으로 표시되는지 여부일 수도 있다고 설명했습니다. 이 경우 앱 클라이언트는 특정 디바이스에서 해당하는 시각적 알림을 제거하여 유니버설 해제가 가능하도록 설정하는 등의 작업 중에서 수행할 작업을 선택할 수 있습니다. 다시 한번 생각해 보면, 앱 클라이언트가 다른 디바이스에서 이 UserNotification 변경 업데이트를 시작한 클라이언트인 경우가 많습니다. UserNotification의 상태를 업데이트하는 시간을 선택할 수 있지만, 일반적으로 사용자가 해당 디바이스에서 해당 시각적 알림을 처리할 때 업데이트되거나 사용자가 사용하도록 설정한 일부 앱 내 환경에서 사용자가 알림을 추가로 처리합니다. 흐름의 예는 다음과 같습니다. 앱 서버에서 사용자 A를 대상으로 하는 알림을 게시합니다. 사용자 A는 앱 클라이언트를 설치한 PC와 휴대폰 모두에서 이 알림을 받습니다. 사용자는 PC에서 알림을 클릭하고, 해당 작업을 처리하는 앱을 추적합니다. 그런 다음, 이 PC의 앱 클라이언트에서 연결된 디바이스 플랫폼 SDK를 호출하여 이 업데이트가 해당 사용자의 모든 디바이스에서 동기화되도록 해당 UserNotification의 상태를 업데이트합니다. 다른 앱 클라이언트는 이 상태 업데이트를 실시간으로 받는 즉시 디바이스의 알림 센터/알림 트레이/알림 센터에서 해당 시각적 경고/메시지/알림 메시지를 제거합니다. 이는 사용자의 디바이스에서 알림이 일반적으로 해제되는 방식입니다.

UserNotification 클래스는 현재 두 가지 유형의 상태 업데이트를 제공합니다. 즉 UserNotificationReadState 또는 UserNotificationUserActionState를 수정하고, 알림이 업데이트되면 수행되어야 하는 작업에 대해 사용자 고유의 논리를 정의할 수 있습니다. 예를 들어 UserActionState를 Activated(활성화됨) 또는 Dismissed(해제됨)로 표시하고, 해당 값을 중심으로 결정하여 유니버설 해제를 구현할 수 있습니다. 또는 ReadState를 Read(읽음) 또는 Unread(읽지 않음)로 표시하고, 이에 따라 앱 내 알림 기록 보기에 표시할 알림을 결정할 수 있습니다. 아래 코드 조각에서는 알림의 UserNotificationUserActionState를 Dismissed로 표시하는 방법을 보여 줍니다.

public void dismissNotification(int position) {
    final UserNotification notification = mNewNotifications.get(position);
          
    notification.setUserActionState(UserNotificationUserActionState.DISMISSED);
    notification.saveAsync();
}