次の方法で共有


アプリにリテール デモ (RDX) 機能を追加する

販売現場で PC やデバイスを試す顧客がすぐに利用できるように、Windows アプリにリテール デモ モードを含めます。

顧客が小売店にいる場合、PC とデバイスのデモを試すことができると予想されます。 多くの場合、リテール デモ エクスペリエンス (RDX)を通じて、アプリを使ってかなりの時間を費やしています。

異なるエクスペリエンスを提供するようにアプリを、通常の モードまたはリテール モードで設定できます。 たとえば、アプリがセットアップ プロセスで始まる場合は、リテール モードでアプリをスキップし、サンプル データと既定の設定をアプリに事前入力して、すぐに開始できます。

お客様の観点から見ると、アプリは 1 つだけです。 顧客が 2 つのモードを区別できるように、アプリがリテール モードの間は、タイトル バーまたは適切な場所に "Retail" という単語が目立つように表示することをお勧めします。

RDX 対応アプリは、アプリの Microsoft Store 要件に加えて、RDX のセットアップ、クリーンアップ、更新のプロセスと互換性があり、お客様が小売店で一貫して肯定的なエクスペリエンスを得られるようにする必要があります。

設計の原則

  • あなたの最高のを見せてください。 小売用デモ体験を使用して、アプリの素晴らしさをアピールしましょう。 顧客にアプリが初めて表示される可能性が高いので、最適な部分を表示してください。

  • 素早くを表示します。 顧客はせっかちになる可能性があります。ユーザーがアプリの真の価値を体験できる時間が速いほど、より良くなります。

  • 物語を簡潔に保つ 小売のデモンストレーション体験は、アプリの価値を短時間で伝えるプレゼンテーションです。

  • エクスペリエンスに焦点を当てます。 ユーザーにコンテンツをダイジェストする時間を与えます。 最適な部分に速く取り込むのは重要ですが、適切な一時停止を設計することは、エクスペリエンスを完全に楽しむのに役立ちます。

技術的な要件

RDX 対応アプリは、小売顧客に最適なアプリをショーケースすることを目的としているため、技術的な要件を満たし、Microsoft Store がすべてのリテール デモ エクスペリエンス アプリに対して持つプライバシー規制を遵守する必要があります。

これは、検証プロセスの準備を行い、テスト プロセスを明確にするために役立つチェックリストとして使用できます。 これらの要件は、検証プロセスだけでなく、リテール デモ エクスペリエンス アプリの有効期間全体にわたって維持する必要があることに注意してください。アプリがリテール デモ デバイスで実行されている限り。

重要な要件

これらの重要な要件を満たしていない RDX 対応アプリは、できるだけ早くすべてのリテール デモ デバイスから削除されます。

  • 個人を特定できる情報 (PII)を要求しないでください。 これには、ログイン情報、Microsoft アカウント情報、または連絡先の詳細が含まれます。

  • エラーのないエクスペリエンス。 アプリはエラーなしで実行する必要があります。 さらに、リテール デモ デバイスを使用しているお客様には、エラーポップアップや通知を表示しないでください。 エラーは、アプリ自体、ブランド、デバイスのブランド、デバイスの製造元のブランド、および Microsoft のブランドに悪影響を及ぼします。

  • 有料アプリには、試用版モードのが必要です。 アプリは無料であるか、試用版モード含まれている必要があります。 顧客は、小売店でのエクスペリエンスに対して支払いを行うことを検討していません。

優先度の高い要件

これらの優先度の高い要件を満たしていない RDX 対応アプリは、直ちに修正プログラムを調査する必要があります。 直ちに修正プログラムが見つからない場合、このアプリはすべてのリテール デモ デバイスから削除される可能性があります。

  • 思い出に残るオフラインエクスペリエンス. デバイスの約 50% がリテールの場所でオフラインになっているので、アプリは優れたオフライン エクスペリエンスを示す必要があります。 これは、オフラインでアプリを操作しているお客様が、引き続き有意義で肯定的なエクスペリエンスを得られるようにするためです。

  • 更新されたコンテンツ体験。 オンライン時にアプリが更新を求められることはありません。 更新が必要な場合は、サイレントで実行する必要があります。

  • 匿名通信はありません。 リテール デモ デバイスを使用している顧客は匿名ユーザーであるため、デバイスからメッセージを送信したり、コンテンツを共有したりすることはできません。

  • クリーンアップ プロセスを使用して一貫したエクスペリエンスを提供します。 小売デモ デバイスに向かうときは、すべての顧客が同じエクスペリエンスを持つ必要があります。 アプリでは、クリーンアップ プロセス を使用して、各使用後に同じ既定の状態に戻す必要があります。 次の顧客に最後の顧客が残したものを見せたくありません。 これには、スコアボード、実績、ロック解除が含まれます。

  • 年齢に適したコンテンツ。 すべてのアプリ コンテンツには、ティーン以下の評価カテゴリを割り当てる必要があります。 詳細については、IARC によるアプリの評価ESRB レーティングを参照してください。

中優先度の要件

Windows リテール ストア チームは、開発者に直接連絡して、これらの問題を解決する方法に関するディスカッションを設定できます。

  • デバイスの範囲で正常に実行する機能。 アプリは、ローエンド仕様のデバイスを含むすべてのデバイスで適切に動作する必要があります。 アプリが最小仕様を満たしていないデバイスにインストールされている場合、アプリはユーザーにこのことを明確に通知する必要があります。 アプリが常に高パフォーマンスで実行できるように、デバイスの最小要件を知る必要があります。

  • 小売店アプリのサイズ要件を満たす。 アプリは 800 MB 未満である必要があります。 RDX 対応アプリがサイズ要件を満たしていない場合は、Windows リテール ストア チームに直接問い合わせてください。

RetailInfo API: デモ モード用のコードの準備

デモモードが有効か

Windows 10 および Windows 11 SDK の Windows.System.Profile 名前空間の一部である RetailInfo ユーティリティ クラスの IsDemoModeEnabled プロパティは、アプリが実行するコード パス (通常の モードまたは リテール モード) を指定するためのブール値インジケーターとして使用されます。

using Windows.Storage;

StorageFolder folder = ApplicationData.Current.LocalFolder;

if (Windows.System.Profile.RetailInfo.IsDemoModeEnabled) 
{
    // Use the demo specific directory
    folder = await folder.GetFolderAsync("demo");
}

StorageFile file = await folder.GetFileAsync("hello.txt");
// Now read from file
using namespace Windows::Storage;

StorageFolder^ localFolder = ApplicationData::Current->LocalFolder;

if (Windows::System::Profile::RetailInfo::IsDemoModeEnabled) 
{
    // Use the demo specific directory
    create_task(localFolder->GetFolderAsync("demo").then([this](StorageFolder^ demoFolder)
    {
        return demoFolder->GetFileAsync("hello.txt");
    }).then([this](task<StorageFile^> fileTask)
    {
        StorageFile^ file = fileTask.get();
    });
    // Do something with file
}
else
{
    create_task(localFolder->GetFileAsync("hello.txt").then([this](StorageFile^ file)
    {
        // Do something with file
    });
}
if (Windows.System.Profile.retailInfo.isDemoModeEnabled) {
    console.log("Retail mode is enabled.");
} else {
    Console.log("Retail mode is not enabled.");
}

RetailInfo.Properties

IsDemoModeEnabled が true を返す場合は、retailInfo.Properties を使用してデバイスに関する一連のプロパティを照会して、よりカスタマイズされたリテール デモ エクスペリエンスを構築できます。 これらの特性には、ManufacturerNameScreensizeMemory などがあります。

using Windows.UI.Xaml.Controls;
using Windows.System.Profile

TextBlock priceText = new TextBlock();
priceText.Text = RetailInfo.Properties[KnownRetailInfo.Price];
// Assume infoPanel is a StackPanel declared in XAML
this.infoPanel.Children.Add(priceText);
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::System::Profile;

TextBlock ^manufacturerText = ref new TextBlock();
manufacturerText.set_Text(RetailInfo::Properties[KnownRetailInfoProperties::Price]);
// Assume infoPanel is a StackPanel declared in XAML
this->infoPanel->Children->Add(manufacturerText);
var pro = Windows.System.Profile;
console.log(pro.retailInfo.properties[pro.KnownRetailInfoProperties.price);

IDL

//  Copyright (c) Microsoft Corporation. All rights reserved.
//
//  WindowsRuntimeAPISet

import "oaidl.idl";
import "inspectable.idl";
import "Windows.Foundation.idl";
#include <sdkddkver.h>

namespace Windows.System.Profile
{
    runtimeclass RetailInfo;
    runtimeclass KnownRetailInfoProperties;

    [version(NTDDI_WINTHRESHOLD), uuid(0712C6B8-8B92-4F2A-8499-031F1798D6EF), exclusiveto(RetailInfo)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    interface IRetailInfoStatics : IInspectable
    {
        [propget] HRESULT IsDemoModeEnabled([out, retval] boolean *value);
        [propget] HRESULT Properties([out, retval, hasvariant] Windows.Foundation.Collections.IMapView<HSTRING, IInspectable *> **value);
    }

    [version(NTDDI_WINTHRESHOLD), uuid(50BA207B-33C4-4A5C-AD8A-CD39F0A9C2E9), exclusiveto(KnownRetailInfoProperties)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    interface IKnownRetailInfoPropertiesStatics : IInspectable
    {
        [propget] HRESULT RetailAccessCode([out, retval] HSTRING *value);
        [propget] HRESULT ManufacturerName([out, retval] HSTRING *value);
        [propget] HRESULT ModelName([out, retval] HSTRING *value);
        [propget] HRESULT DisplayModelName([out, retval] HSTRING *value);
        [propget] HRESULT Price([out, retval] HSTRING *value);
        [propget] HRESULT IsFeatured([out, retval] HSTRING *value);
        [propget] HRESULT FormFactor([out, retval] HSTRING *value);
        [propget] HRESULT ScreenSize([out, retval] HSTRING *value);
        [propget] HRESULT Weight([out, retval] HSTRING *value);
        [propget] HRESULT DisplayDescription([out, retval] HSTRING *value);
        [propget] HRESULT BatteryLifeDescription([out, retval] HSTRING *value);
        [propget] HRESULT ProcessorDescription([out, retval] HSTRING *value);
        [propget] HRESULT Memory([out, retval] HSTRING *value);
        [propget] HRESULT StorageDescription([out, retval] HSTRING *value);
        [propget] HRESULT GraphicsDescription([out, retval] HSTRING *value);
        [propget] HRESULT FrontCameraDescription([out, retval] HSTRING *value);
        [propget] HRESULT RearCameraDescription([out, retval] HSTRING *value);
        [propget] HRESULT HasNfc([out, retval] HSTRING *value);
        [propget] HRESULT HasSdSlot([out, retval] HSTRING *value);
        [propget] HRESULT HasOpticalDrive([out, retval] HSTRING *value);
        [propget] HRESULT IsOfficeInstalled([out, retval] HSTRING *value);
        [propget] HRESULT WindowsVersion([out, retval] HSTRING *value);
    }

    [version(NTDDI_WINTHRESHOLD), static(IRetailInfoStatics, NTDDI_WINTHRESHOLD)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone), static(IRetailInfoStatics, NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    [threading(both)]
    [marshaling_behavior(agile)]
    runtimeclass RetailInfo
    {
    }

    [version(NTDDI_WINTHRESHOLD), static(IKnownRetailInfoPropertiesStatics, NTDDI_WINTHRESHOLD)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone), static(IKnownRetailInfoPropertiesStatics, NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    [threading(both)]
    [marshaling_behavior(agile)]
    runtimeclass KnownRetailInfoProperties
    {
    }
}

クリーンアップ プロセス

クリーンアップは、買い物客がデバイスとの対話を停止してから 2 分後に開始されます。 リテール デモが再生され、Windows は連絡先、写真、その他のアプリのサンプル データのリセットを開始します。 デバイスによっては、すべてが正常にリセットされるまでに 1 ~ 5 分かかる場合があります。 これにより、小売店内のすべての顧客がデバイスまで歩いて行き、デバイスを操作するときに同じエクスペリエンスを得ることができます。

手順 1: クリーンアップ

  • すべての Win32 アプリとストア アプリが閉じられます
  • 画像ビデオミュージックドキュメントSavePicturesCameraRollデスクトップダウンロードなどの既知のフォルダー内のすべてのファイル フォルダーは削除されます
  • 非構造化ローミング状態と構造化ローミング状態が削除される
  • 構造化されたローカル状態が削除される

手順 2: セットアップ

  • オフライン デバイスの場合: フォルダーは空のまま
  • オンライン デバイスの場合: リテール デモアセットを Microsoft Store からデバイスにプッシュできます

ユーザー セッション間でデータを格納する

ユーザー セッション間でデータを格納するには、既定のクリーンアップ プロセスではこのフォルダー内のデータが自動的に削除されないため、ApplicationData.Current.TemporaryFolder に情報を格納できます。 LocalState を使用して格納された情報は、クリーンアップ プロセス中に削除されることに注意してください。

クリーンアップ プロセスをカスタマイズする

クリーンアップ プロセスをカスタマイズするには、Microsoft-RetailDemo-Cleanup App Service をアプリに実装します。

カスタムクリーンアップロジックが必要なシナリオには、広範なセットアップの実行、データのダウンロードとキャッシュ、LocalState データを削除したくない場合が含まれます。

手順 1: アプリ マニフェストで microsoft-RetailDemo-Cleanup サービス を宣言します。

  <Applications>
      <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="MyCompany.MyApp.RDXCustomCleanupTask">
          <uap:AppService Name="Microsoft-RetailDemo-Cleanup" />
        </uap:Extension>
      </Extensions>
   </Application>
  </Applications>

手順 2: 次のサンプル テンプレートを使用して、AppdataCleanup case 関数の下にカスタム クリーンアップ ロジックを実装します。

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.Storage;

namespace MyCompany.MyApp
{
    public sealed class RDXCustomCleanupTask : IBackgroundTask
    {
        BackgroundTaskCancellationReason _cancelReason = BackgroundTaskCancellationReason.Abort;
        BackgroundTaskDeferral _deferral = null;
        IBackgroundTaskInstance _taskInstance = null;
        AppServiceConnection _appServiceConnection = null;

        const string MessageCommand = "Command";

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get the deferral object from the task instance, and take a reference to the taskInstance;
            _deferral = taskInstance.GetDeferral();
            _taskInstance = taskInstance;
            _taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);

            AppServiceTriggerDetails appService = _taskInstance.TriggerDetails as AppServiceTriggerDetails;
            if ((appService != null) && (appService.Name == "Microsoft-RetailDemo-Cleanup"))
            {
                _appServiceConnection = appService.AppServiceConnection;
                _appServiceConnection.RequestReceived += _appServiceConnection_RequestReceived;
                _appServiceConnection.ServiceClosed += _appServiceConnection_ServiceClosed;
            }
            else
            {
                _deferral.Complete();
            }
        }

        void _appServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
        }

        async void _appServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            //Get a deferral because we will be calling async code
            AppServiceDeferral requestDeferral = args.GetDeferral();
            string command = null;
            var returnData = new ValueSet();

            try
            {
                ValueSet message = args.Request.Message;
                if (message.ContainsKey(MessageCommand))
                {
                    command = message[MessageCommand] as string;
                }

                if (command != null)
                {
                    switch (command)
                    {
                        case "AppdataCleanup":
                            {
                                // Do custom clean up logic here
                                break;
                            }
                    }
                }
            }
            catch (Exception e)
            {
            }
            finally
            {
                requestDeferral.Complete();
                // Also release the task deferral since we only process one request per instance.
                _deferral.Complete();
            }
        }

        private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            _cancelReason = reason;
        }
    }
}