アプリへの市販デモ (RDX) 機能の追加
販売フロアで PC やデバイスを試しているユーザーがすぐに開始できるように、Windows アプリに小売デモ モードを含めます。
お客様は、小売店で PC やデバイスのデモを試用できることを期待しています。 多くの場合、市販デモ エクスペリエンス (RDX) を通じて、かなりの時間をアプリの試用に費やします。
"通常" モードと "市販" モードで異なるエクスペリエンスを提供するようにアプリを設定することができます。 たとえば、アプリが設定 プロセスから始まる場合、市販モードではそのプロセスをスキップし、アプリにサンプル データと既定の設定をあらかじめ登録しておき、お客様がすぐに使用できるようにすることができます。
お客様の視点から見えるアプリは 1 つのみです。 お客様が 2 つのモードを区別できるように、アプリが市販モードの間は、タイトル バーや適切な場所に "市販" という単語を目立つように表示することをお勧めします。
RDX 対応アプリは、ご来店のお客様に常に肯定的なエクスペリエンスを提供できるように、アプリの Microsoft Store 要件に加え、RDX の設定、クリーンアップ、プロセスの更新に完全に対応している必要があります。
設計原則
最大のメリットを提示する。 市販デモ モードは、アプリのメリットを伝えるために使ってください。 多くの場合、これはお客様がこのアプリに触れる最初の機会です。最も魅力的な部分をアピールしましょう。
スピーディな伝達。 顧客はせっかちになる可能性があります。ユーザーがアプリの真の価値を体験できる時間が速いほど、より良くなります。
シンプルなストーリー。 市販デモ モードは、ごく限られた時間でアプリの真価を伝えるチャンスです。
エクスペリエンスを重視。 ユーザーにコンテンツをダイジェストする時間を与えます。 最適な部分に速く取り込むのは重要ですが、適切な一時停止を設計することは、エクスペリエンスを完全に楽しむのに役立ちます。
技術的な要件
RDX 対応アプリは、ご来店のお客様にアプリの真価をご理解いただくことを目的としているため、次の技術的要件を満たすと共に、すべての市販デモ エクスペリエンス アプリに関して Microsoft Store が定めるプライバシー規則に従う必要があります。
これをチェックリストとして利用して、検証プロセスの準備や、テスト プロセスの明確化に役立てることができます。 これらの要件は、検証プロセスだけでなく、リテール デモ エクスペリエンス アプリの有効期間全体にわたって維持する必要があることに注意してください。アプリがリテール デモ デバイスで実行されている限り。
重要な要件
これらの必須要件を満たしていない RDX 対応アプリは、可能な限り速やかにすべての市販デモ デバイスから削除されます。
個人を特定できる情報 (PII) を尋ねない。 これには、ログイン情報、Microsoft アカウント情報、連絡先の詳細が含まれます。
エラーのないエクスペリエンス。 アプリはエラーなしで実行する必要があります。 さらに、リテール デモ デバイスを使用しているお客様には、エラーポップアップや通知を表示しないでください。 エラーは、アプリ自体、ブランド、デバイスのブランド、デバイスの製造元のブランド、Microsoft のブランドに悪影響を及ぼします。
有料アプリには試用モードを用意する。 アプリは無料であるか、試用モードを含める必要があります。 顧客は、小売店でのエクスペリエンスに対して支払いを行うことを検討していません。
優先度の高い要件
これらの優先度の高い要件を満たしていない RDX 対応アプリは、直ちに調査して修正する必要があります。 直ちに修正プログラムが見つからない場合、このアプリはすべてのリテール デモ デバイスから削除される可能性があります。
優れたオフライン エクスペリエンス。 小売拠点に展示されているデバイスの約 50% がオフラインであるため、アプリは優れたオフライン エクスペリエンスを提供する必要があります。 これは、オフラインでアプリを操作しているお客様が、引き続き有意義で肯定的なエクスペリエンスを得られるようにするためです。
更新済みのコンテンツ エクスペリエンス。 オンラインのときに、更新プログラムのプロンプトを表示しないでください。 更新が必要な場合は、プロンプトを表示せずに実行するようにします。
匿名通信の禁止。 市販デモ デバイスを使うお客様は匿名ユーザーであるため、デバイスからのメッセージ送信やコンテンツの共有を抑制する必要があります。
クリーンアップ プロセスを使って一貫したエクスペリエンス提供する。 小売デモ デバイスに向かうときは、すべての顧客が同じエクスペリエンスを持つ必要があります。 クリーンアップ プロセスを使って、アプリの使用後は常に同じ既定の状態に戻るようにします。 前のお客様が残したものを次のお客様に見せないでください。 これには、スコアボード、実績、ロック解除が含まれます。
年齢に応じた適切なコンテンツ。 すべてのアプリのコンテンツは、ティーン以下の年齢区分向けでなければなりません。 詳細については、アプリの評価に関する IARC のページと ESRB 評価に関するページを参照してください。
中程度の優先度の要件
Windows リテール ストア チームは、開発者に直接連絡して、これらの問題を解決する方法に関するディスカッションを設定できます。
多様なデバイスで正常に動作する能力。 アプリは、ローエンド仕様のデバイスを含む、すべてのデバイスで適切に動作する必要があります。 最小限の仕様を満たさないデバイスにアプリがインストールされた場合は、そのことをユーザーに明確に通知する必要があります。 アプリが常に高パフォーマンスで実行できるように、デバイスの最小要件を知る必要があります。
小売店アプリのサイズ要件を満たす。 アプリは 800 MB 未満である必要があります。 RDX 対応アプリがこのサイズ要件を満たしていない場合は、Windows リテール ストア チームに直接お問い合わせください。
RetailInfo API: デモ モード用のコードの準備
IsDemoModeEnabled
Windows 10 および Windows 11 SDK の Windows.System.Profile 名前空間の一部である RetailInfo ユーティリティ クラスの IsDemoModeEnabled プロパティは、アプリを実行するコード パス (正規モードまたは retail モード) を指定するためのブール値インジケーターとして使用されます。
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 を使用してデバイスに関する一連のプロパティを照会して、よりカスタマイズされたリテール デモ エクスペリエンスを構築できます。 これらのプロパティには、 ManufacturerName、 Screensize、 Memory などがあります。
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 アプリとストア アプリが閉じられます
- Pictures、Videos、Music、Documents、SavedPictures、CameraRoll、Desktop、Downloads などの既知のフォルダー内のすべてのファイルが削除されますフォルダーは削除されます。
- 非構造化ローミング状態と構造化ローミング状態が削除される
- 構造化されたローカル状態が削除される
手順 2: 設定
- オフライン デバイスの場合: フォルダーは空のまま
- オンライン デバイスの場合: リテール デモアセットを Microsoft Store からデバイスにプッシュできます
ユーザー セッションをまたいでデータを格納する
ユーザー セッションをまたいでデータを格納する場合は、情報を ApplicationData.Current.TemporaryFolder に格納します。このフォルダーのデータは、既定のクリーンアップ処理によって自動的に削除されません。 LocalState を使って保存した情報は、クリーンアップ処理中に削除されることに注意してください。
クリーンアップ プロセスをカスタマイズする
クリーンアップ プロセスをカスタマイズするには、アプリに Microsoft-RetailDemo-Cleanup
アプリ サービスを実装します。
カスタムのクリーンアップ ロジックが必要なシナリオには、負荷の高い設定の実行、データのダウンロードとキャッシュ、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 ケース関数の下に、カスタムのクリーンアップ ロジックを実装します。
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;
}
}
}