diff --git a/MRETestBed/Assets/Scenes/HelloWorld.unity b/MRETestBed/Assets/Scenes/HelloWorld.unity
index be0ea848..d195011c 100644
--- a/MRETestBed/Assets/Scenes/HelloWorld.unity
+++ b/MRETestBed/Assets/Scenes/HelloWorld.unity
@@ -222,6 +222,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 276664683}
diff --git a/MRETestBed/Assets/Scenes/Standalone.unity b/MRETestBed/Assets/Scenes/Standalone.unity
index 754dfb26..57f6a761 100644
--- a/MRETestBed/Assets/Scenes/Standalone.unity
+++ b/MRETestBed/Assets/Scenes/Standalone.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 1966163892}
- m_IndirectSpecularColor: {r: 0.45135534, g: 0.5009366, b: 0.5727883, a: 1}
+ m_IndirectSpecularColor: {r: 0.45135576, g: 0.500937, b: 0.57278883, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -234,6 +234,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 276664683}
diff --git a/MRETestBed/Assets/Scenes/SynchronizationTest-localhost.unity b/MRETestBed/Assets/Scenes/SynchronizationTest-localhost.unity
index 7dc68d71..e7866ba7 100644
--- a/MRETestBed/Assets/Scenes/SynchronizationTest-localhost.unity
+++ b/MRETestBed/Assets/Scenes/SynchronizationTest-localhost.unity
@@ -293,6 +293,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 368773028}
PlaceholderObject: {fileID: 164408130}
UserGameObject: {fileID: 1767571516}
@@ -517,6 +518,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 582437838}
UserGameObject: {fileID: 1767571516}
diff --git a/MRETestBed/Assets/Scenes/TriggerVolumeTestBed-localhost.unity b/MRETestBed/Assets/Scenes/TriggerVolumeTestBed-localhost.unity
index 3c0cb566..6977219f 100644
--- a/MRETestBed/Assets/Scenes/TriggerVolumeTestBed-localhost.unity
+++ b/MRETestBed/Assets/Scenes/TriggerVolumeTestBed-localhost.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641258, b: 0.5748172, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -435,6 +435,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 18576793}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 365627181}
diff --git a/MRETestBed/Assets/TestBed Assets/Scripts/MREComponent.cs b/MRETestBed/Assets/TestBed Assets/Scripts/MREComponent.cs
index 18c5f7b1..ea734cba 100644
--- a/MRETestBed/Assets/TestBed Assets/Scripts/MREComponent.cs
+++ b/MRETestBed/Assets/TestBed Assets/Scripts/MREComponent.cs
@@ -6,7 +6,7 @@
using MixedRealityExtension.API;
using MixedRealityExtension.App;
using MixedRealityExtension.Assets;
-using MixedRealityExtension.Core.Interfaces;
+using MixedRealityExtension.Core;
using MixedRealityExtension.Factories;
using MixedRealityExtension.PluginInterfaces;
using MixedRealityExtension.RPC;
@@ -62,6 +62,9 @@ public class UserProperty
public bool AutoJoin = true;
+ [SerializeField]
+ private Permissions GrantedPermissions;
+
public Transform SceneRoot;
public GameObject PlaceholderObject;
@@ -131,7 +134,6 @@ void Start()
defaultMaterial: DefaultPrimMaterial,
layerApplicator: new SimpleLayerApplicator(0, 9, 10, 5),
assetCache: assetCache,
- behaviorFactory: new BehaviorFactory(),
textFactory: new TmpTextFactory()
{
DefaultFont = DefaultFont,
@@ -140,12 +142,14 @@ void Start()
MonospaceFont = MonospaceFont,
CursiveFont = CursiveFont
},
- libraryFactory: new ResourceFactory(),
- userInfoProvider: new UserInfoProvider(),
+ permissionManager: new SimplePermissionManager(GrantedPermissions),
+ behaviorFactory: new BehaviorFactory(),
dialogFactory: DialogFactory,
- logger: new MRELogger(),
+ libraryFactory: new ResourceFactory(),
+ gltfImporterFactory: new VertexShadedGltfImporterFactory(),
materialPatcher: new VertexMaterialPatcher(),
- gltfImporterFactory: new VertexShadedGltfImporterFactory()
+ userInfoProvider: new UserInfoProvider(),
+ logger: new MRELogger()
);
_apiInitialized = true;
}
diff --git a/MRETestBed_MRTK/Assets/Scenes/HelloWorld_2D.unity b/MRETestBed_MRTK/Assets/Scenes/HelloWorld_2D.unity
index 22ddd37d..b5c2f6cc 100644
--- a/MRETestBed_MRTK/Assets/Scenes/HelloWorld_2D.unity
+++ b/MRETestBed_MRTK/Assets/Scenes/HelloWorld_2D.unity
@@ -241,6 +241,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 276664683}
@@ -252,6 +253,7 @@ MonoBehaviour:
DefaultPrimMaterial: {fileID: 2100000, guid: 265cb5b418966a64ea212597d469fa33, type: 2}
DialogFactory: {fileID: 114153473817070278, guid: f1f77a0b742ec0841858c35dc9348515,
type: 3}
+ EnableMRTK: 0
--- !u!114 &1141388992
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/MRETestBed_MRTK/Assets/Scenes/Localhost_MRTK.unity b/MRETestBed_MRTK/Assets/Scenes/Localhost_MRTK.unity
index 821ccdd1..b69b0f28 100644
--- a/MRETestBed_MRTK/Assets/Scenes/Localhost_MRTK.unity
+++ b/MRETestBed_MRTK/Assets/Scenes/Localhost_MRTK.unity
@@ -220,6 +220,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 276568010}
diff --git a/MRETestBed_MRTK/Assets/Scenes/Standalone_2D.unity b/MRETestBed_MRTK/Assets/Scenes/Standalone_2D.unity
index 754dfb26..44181c1b 100644
--- a/MRETestBed_MRTK/Assets/Scenes/Standalone_2D.unity
+++ b/MRETestBed_MRTK/Assets/Scenes/Standalone_2D.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 1966163892}
- m_IndirectSpecularColor: {r: 0.45135534, g: 0.5009366, b: 0.5727883, a: 1}
+ m_IndirectSpecularColor: {r: 0.45135576, g: 0.500937, b: 0.57278883, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -234,6 +234,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 276664683}
@@ -244,6 +245,7 @@ MonoBehaviour:
CursiveFont: {fileID: 11400000, guid: b31d2923d654c0e49b3e196355c7878e, type: 2}
DefaultPrimMaterial: {fileID: 2100000, guid: 265cb5b418966a64ea212597d469fa33, type: 2}
DialogFactory: {fileID: 36258468}
+ EnableMRTK: 0
--- !u!114 &1141388992
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/MRETestBed_MRTK/Assets/Scenes/SynchronizationTest-localhost_2D.unity b/MRETestBed_MRTK/Assets/Scenes/SynchronizationTest-localhost_2D.unity
index 7dc68d71..9cae3889 100644
--- a/MRETestBed_MRTK/Assets/Scenes/SynchronizationTest-localhost_2D.unity
+++ b/MRETestBed_MRTK/Assets/Scenes/SynchronizationTest-localhost_2D.unity
@@ -293,6 +293,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 368773028}
PlaceholderObject: {fileID: 164408130}
UserGameObject: {fileID: 1767571516}
@@ -304,6 +305,7 @@ MonoBehaviour:
DefaultPrimMaterial: {fileID: 2100000, guid: 265cb5b418966a64ea212597d469fa33, type: 2}
DialogFactory: {fileID: 114153473817070278, guid: f1f77a0b742ec0841858c35dc9348515,
type: 3}
+ EnableMRTK: 0
--- !u!4 &368773028
Transform:
m_ObjectHideFlags: 0
@@ -517,6 +519,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 1141388990}
PlaceholderObject: {fileID: 582437838}
UserGameObject: {fileID: 1767571516}
@@ -528,6 +531,7 @@ MonoBehaviour:
DefaultPrimMaterial: {fileID: 2100000, guid: 265cb5b418966a64ea212597d469fa33, type: 2}
DialogFactory: {fileID: 114153473817070278, guid: f1f77a0b742ec0841858c35dc9348515,
type: 3}
+ EnableMRTK: 0
--- !u!1 &1256608368
GameObject:
m_ObjectHideFlags: 0
diff --git a/MRETestBed_MRTK/Assets/Scenes/TriggerVolumeTestBed-localhost_2D.unity b/MRETestBed_MRTK/Assets/Scenes/TriggerVolumeTestBed-localhost_2D.unity
index 3c0cb566..2ee07d07 100644
--- a/MRETestBed_MRTK/Assets/Scenes/TriggerVolumeTestBed-localhost_2D.unity
+++ b/MRETestBed_MRTK/Assets/Scenes/TriggerVolumeTestBed-localhost_2D.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641258, b: 0.5748172, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -435,6 +435,7 @@ MonoBehaviour:
UserProperties: []
AutoStart: 0
AutoJoin: 1
+ GrantedPermissions: -1
SceneRoot: {fileID: 18576793}
PlaceholderObject: {fileID: 0}
UserGameObject: {fileID: 365627181}
@@ -446,6 +447,7 @@ MonoBehaviour:
DefaultPrimMaterial: {fileID: 2100000, guid: 265cb5b418966a64ea212597d469fa33, type: 2}
DialogFactory: {fileID: 114153473817070278, guid: f1f77a0b742ec0841858c35dc9348515,
type: 3}
+ EnableMRTK: 0
--- !u!1 &2044912625
GameObject:
m_ObjectHideFlags: 0
diff --git a/MRETestBed_MRTK/Assets/TestBed Assets/Scripts/MREComponent.cs b/MRETestBed_MRTK/Assets/TestBed Assets/Scripts/MREComponent.cs
index 747ed57d..b636225e 100644
--- a/MRETestBed_MRTK/Assets/TestBed Assets/Scripts/MREComponent.cs
+++ b/MRETestBed_MRTK/Assets/TestBed Assets/Scripts/MREComponent.cs
@@ -6,6 +6,7 @@
using MixedRealityExtension.API;
using MixedRealityExtension.App;
using MixedRealityExtension.Assets;
+using MixedRealityExtension.Core;
using MixedRealityExtension.Core.Interfaces;
using MixedRealityExtension.Factories;
using MixedRealityExtension.PluginInterfaces;
@@ -63,6 +64,9 @@ public class UserProperty
public bool AutoJoin = true;
+ [SerializeField]
+ private Permissions GrantedPermissions;
+
public Transform SceneRoot;
public GameObject PlaceholderObject;
@@ -135,7 +139,6 @@ void Start()
defaultMaterial: DefaultPrimMaterial,
layerApplicator: new SimpleLayerApplicator(0, 9, 10, 5),
assetCache: assetCache,
- behaviorFactory: new MRTKBehaviorFactory(),
textFactory: new TmpTextFactory()
{
DefaultFont = DefaultFont,
@@ -144,12 +147,14 @@ void Start()
MonospaceFont = MonospaceFont,
CursiveFont = CursiveFont
},
- libraryFactory: new ResourceFactory(),
- userInfoProvider: new UserInfoProvider(),
+ permissionManager: new SimplePermissionManager(GrantedPermissions),
+ behaviorFactory: new BehaviorFactory(),
dialogFactory: DialogFactory,
- logger: new MRELogger(),
+ libraryFactory: new ResourceFactory(),
+ gltfImporterFactory: new VertexShadedGltfImporterFactory(),
materialPatcher: new VertexMaterialPatcher(),
- gltfImporterFactory: new VertexShadedGltfImporterFactory()
+ userInfoProvider: new UserInfoProvider(),
+ logger: new MRELogger()
);
_apiInitialized = true;
}
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/API/MREApi.cs b/MREUnityRuntime/MREUnityRuntimeLib/API/MREApi.cs
index d8e66d34..a3143663 100644
--- a/MREUnityRuntime/MREUnityRuntimeLib/API/MREApi.cs
+++ b/MREUnityRuntime/MREUnityRuntimeLib/API/MREApi.cs
@@ -26,43 +26,54 @@ public static class MREAPI
/// The material template used for all SDK-spawned meshes.
/// The class used to apply MRE layers to Unity colliders.
/// The class responsible for long-term asset caching.
- /// The behavior factory to use within the runtime.
/// The text factory to use within the runtime.
- /// The primitive factory to use within the runtime.
+ /// The instance responsible for presenting users with permission requests.
+ /// The behavior factory to use within the runtime.
+ ///
/// The library resource factory to use within the runtime.
+ ///
+ /// The primitive factory to use within the runtime.
/// The glTF loader factory. Uses default GLTFSceneImporter if omitted.
/// Overrides default material property map (color and mainTexture only).
- ///
/// Provides appId/sessionId scoped IUserInfo instances.
- ///
/// The logger to be used by the MRE SDK.
public static void InitializeAPI(
+ // required properties
UnityEngine.Material defaultMaterial,
ILayerApplicator layerApplicator,
IAssetCache assetCache,
+ ITextFactory textFactory,
+ IPermissionManager permissionManager,
+ // missing features if omitted
IBehaviorFactory behaviorFactory = null,
- ITextFactory textFactory = null,
- IPrimitiveFactory primitiveFactory = null,
+ IDialogFactory dialogFactory = null,
ILibraryResourceFactory libraryFactory = null,
+ IVideoPlayerFactory videoPlayerFactory = null,
+ // reasonable defaults provided
+ IPrimitiveFactory primitiveFactory = null,
IGLTFImporterFactory gltfImporterFactory = null,
IMaterialPatcher materialPatcher = null,
- IVideoPlayerFactory videoPlayerFactory = null,
IUserInfoProvider userInfoProvider = null,
- IDialogFactory dialogFactory = null,
IMRELogger logger = null)
{
+ // required properties
AppsAPI.DefaultMaterial = defaultMaterial;
AppsAPI.LayerApplicator = layerApplicator;
AppsAPI.AssetCache = assetCache;
+ AppsAPI.TextFactory = textFactory;
+ AppsAPI.PermissionManager = permissionManager;
+
+ // missing features if omitted
AppsAPI.BehaviorFactory = behaviorFactory;
- AppsAPI.TextFactory = textFactory ?? throw new ArgumentException($"{nameof(textFactory)} cannot be null");
- AppsAPI.PrimitiveFactory = primitiveFactory ?? new MWPrimitiveFactory();
+ AppsAPI.DialogFactory = dialogFactory;
AppsAPI.LibraryResourceFactory = libraryFactory;
AppsAPI.VideoPlayerFactory = videoPlayerFactory;
+
+ // reasonable defaults provided
+ AppsAPI.PrimitiveFactory = primitiveFactory ?? new MWPrimitiveFactory();
AppsAPI.GLTFImporterFactory = gltfImporterFactory ?? new GLTFImporterFactory();
AppsAPI.MaterialPatcher = materialPatcher ?? new DefaultMaterialPatcher();
AppsAPI.UserInfoProvider = userInfoProvider ?? new NullUserInfoProvider();
- AppsAPI.DialogFactory = dialogFactory;
#if ANDROID_DEBUG
Logger = logger ?? new UnityLogger(null);
@@ -128,6 +139,8 @@ public class MREAppsAPI
internal IDialogFactory DialogFactory { get; set; }
+ internal IPermissionManager PermissionManager { get; set; }
+
///
/// Creates a new mixed reality extension app and adds it to the MRE runtime.
///
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/App/AppManifest.cs b/MREUnityRuntime/MREUnityRuntimeLib/App/AppManifest.cs
new file mode 100644
index 00000000..f146a556
--- /dev/null
+++ b/MREUnityRuntime/MREUnityRuntimeLib/App/AppManifest.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using MixedRealityExtension.Core;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MixedRealityExtension.Core
+{
+ ///
+ /// Class containing author-provided metadata about an MRE instance
+ ///
+ public class AppManifest
+ {
+ ///
+ /// A human-readable name for this MRE
+ ///
+ public string Name;
+
+ ///
+ /// A human readable description of this MRE's behavior
+ ///
+ public string Description;
+
+ ///
+ /// The MRE's author name and/or contact information
+ ///
+ public string Author;
+
+ ///
+ /// The license for the MRE's source code
+ ///
+ public string License;
+
+ ///
+ /// The location of the MRE's public source code
+ ///
+ public string RepositoryUrl;
+
+ ///
+ /// A list of permissions required for this MRE to run
+ ///
+ public Permissions[] Permissions;
+
+ ///
+ /// A list of permissions that this MRE can use, but are not required
+ ///
+ public Permissions[] OptionalPermissions;
+
+ internal static async Task DownloadManifest(Uri manifestUri)
+ {
+ var webClient = new HttpClient();
+ var response = await webClient.GetAsync(manifestUri, HttpCompletionOption.ResponseContentRead);
+ if (response.IsSuccessStatusCode)
+ {
+ var manifestString = await response.Content.ReadAsStringAsync();
+ return JsonConvert.DeserializeObject(manifestString, Constants.SerializerSettings);
+ }
+ else
+ {
+ return new AppManifest();
+ }
+ }
+ }
+}
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/App/MixedRealityExtensionApp.cs b/MREUnityRuntime/MREUnityRuntimeLib/App/MixedRealityExtensionApp.cs
index d417c847..1fb566bb 100644
--- a/MREUnityRuntime/MREUnityRuntimeLib/App/MixedRealityExtensionApp.cs
+++ b/MREUnityRuntime/MREUnityRuntimeLib/App/MixedRealityExtensionApp.cs
@@ -56,6 +56,10 @@ internal sealed class MixedRealityExtensionApp : IMixedRealityExtensionApp, ICom
private enum AppState
{
Stopped,
+ ///
+ /// Startup has been called, but we might be waiting for permission to run.
+ ///
+ WaitingForPermission,
Starting,
Running
}
@@ -63,6 +67,9 @@ private enum AppState
private AppState _appState = AppState.Stopped;
private int generation = 0;
+ [Obsolete]
+ private string PlatformId;
+
public IMRELogger Logger { get; private set; }
#region Events - Public
@@ -131,6 +138,8 @@ public event MWEventHandler OnActorCreated
///
public RPCChannelInterface RPCChannels { get; }
+ public AssetManager AssetManager => _assetManager;
+
#endregion
#region Properties - Internal
@@ -151,7 +160,7 @@ public event MWEventHandler OnActorCreated
internal AssetLoader AssetLoader => _assetLoader;
- public AssetManager AssetManager => _assetManager;
+ internal Permissions GrantedPermissions = Permissions.None;
#endregion
@@ -220,10 +229,47 @@ private void OnRigidBodyRemoved(Guid id)
}
///
- public void Startup(string url, string sessionId, string platformId)
+ public async void Startup(string url, string sessionId, string platformId)
{
+ if (_appState != AppState.Stopped)
+ {
+ Shutdown();
+ }
+
ServerUri = new Uri(url, UriKind.Absolute);
ServerAssetUri = new Uri(Regex.Replace(ServerUri.AbsoluteUri, "^ws(s?):", "http$1:"));
+ SessionId = sessionId;
+ PlatformId = platformId;
+
+ _appState = AppState.WaitingForPermission;
+
+ // download manifest
+ var manifestUri = new Uri(ServerAssetUri, "./manifest.json");
+ var manifest = await AppManifest.DownloadManifest(manifestUri);
+ var neededFlags = Permissions.Execution | (manifest.Permissions?.ToFlags() ?? Permissions.None);
+ var wantedFlags = manifest.OptionalPermissions?.ToFlags() ?? Permissions.None;
+
+ // get permission to run from host app
+ var grantedPerms = await MREAPI.AppsAPI.PermissionManager.PromptForPermissions(
+ appLocation: ServerUri,
+ permissionsNeeded: new HashSet(manifest.Permissions ?? new Permissions[0]) { Permissions.Execution },
+ permissionsWanted: manifest.OptionalPermissions,
+ permissionFlagsNeeded: neededFlags,
+ permissionFlagsWanted: wantedFlags,
+ appManifest: manifest);
+
+ // only use permissions that are requested, even if the user offers more
+ GrantedPermissions = grantedPerms & (neededFlags | wantedFlags);
+
+ MREAPI.AppsAPI.PermissionManager.OnPermissionDecisionsChanged += OnPermissionsUpdated;
+
+ if (!grantedPerms.HasFlag(Permissions.Execution))
+ {
+ Debug.LogError($"User has denied permission for the MRE '{ServerUri}' to run");
+ return;
+ }
+
+ _appState = AppState.Starting;
if (UsePhysicsBridge)
{
@@ -232,31 +278,21 @@ public void Startup(string url, string sessionId, string platformId)
_actorManager.RigidBodyKinematicsChanged += OnRigidBodyKinematicsChanged;
_actorManager.RigidBodyOwnerChanged += OnRigidBodyOwnerChanged;
}
+
+ var connection = new WebSocket();
+ connection.Url = url;
+ connection.Headers.Add(Constants.SessionHeader, SessionId);
+ connection.Headers.Add(Constants.PlatformHeader, PlatformId);
+ connection.Headers.Add(Constants.LegacyProtocolVersionHeader, $"{Constants.LegacyProtocolVersion}");
+ connection.Headers.Add(Constants.CurrentClientVersionHeader, Constants.CurrentClientVersion);
+ connection.Headers.Add(Constants.MinimumSupportedSDKVersionHeader, Constants.MinimumSupportedSDKVersion);
+ connection.OnConnecting += Conn_OnConnecting;
+ connection.OnConnectFailed += Conn_OnConnectFailed;
+ connection.OnConnected += Conn_OnConnected;
+ connection.OnDisconnected += Conn_OnDisconnected;
+ connection.OnError += Connection_OnError;
+ _conn = connection;
- if (_conn == null)
- {
- if (_appState == AppState.Stopped)
- {
- _appState = AppState.Starting;
- }
-
- SessionId = sessionId;
-
- var connection = new WebSocket();
-
- connection.Url = url;
- connection.Headers.Add(Constants.SessionHeader, sessionId);
- connection.Headers.Add(Constants.PlatformHeader, platformId);
- connection.Headers.Add(Constants.LegacyProtocolVersionHeader, $"{Constants.LegacyProtocolVersion}");
- connection.Headers.Add(Constants.CurrentClientVersionHeader, Constants.CurrentClientVersion);
- connection.Headers.Add(Constants.MinimumSupportedSDKVersionHeader, Constants.MinimumSupportedSDKVersion);
- connection.OnConnecting += Conn_OnConnecting;
- connection.OnConnectFailed += Conn_OnConnectFailed;
- connection.OnConnected += Conn_OnConnected;
- connection.OnDisconnected += Conn_OnDisconnected;
- connection.OnError += Connection_OnError;
- _conn = connection;
- }
_conn.Open();
}
@@ -266,6 +302,15 @@ private void OnRigidBodyOwnerChanged(Guid id, Guid? owner)
_physicsBridge.setRigidBodyOwnership(id, isOwner);
}
+ private void OnPermissionsUpdated(Uri serverUri, Permissions oldPermissions, Permissions newPermissions)
+ {
+ if (serverUri.GetLeftPart(UriPartial.Path) == ServerUri.GetLeftPart(UriPartial.Path)
+ && _appState != AppState.Stopped)
+ {
+ Startup(ServerUri.ToString(), SessionId, PlatformId);
+ }
+ }
+
///
private void Disconnect()
{
@@ -300,6 +345,8 @@ public void Shutdown()
Disconnect();
FreeResources();
+ MREAPI.AppsAPI.PermissionManager.OnPermissionDecisionsChanged -= OnPermissionsUpdated;
+
if (_appState != AppState.Stopped)
{
_appState = AppState.Stopped;
@@ -396,6 +443,13 @@ public void UserJoin(GameObject userGO, IUserInfo userInfo)
{
void PerformUserJoin()
{
+ // only join the user if required
+ if (!GrantedPermissions.HasFlag(Permissions.UserInteraction)
+ && !GrantedPermissions.HasFlag(Permissions.UserTracking))
+ {
+ return;
+ }
+
var user = userGO.GetComponents()
.FirstOrDefault(_user => _user.AppInstanceId == this.InstanceId);
@@ -995,6 +1049,14 @@ private void OnShowDialog(ShowDialog payload, Action onCompleteCallback)
);
onCompleteCallback?.Invoke();
}
+ else if (!GrantedPermissions.HasFlag(Permissions.UserInteraction))
+ {
+ Protocol.Send(
+ new DialogResponse() { FailureMessage = "The user has refused the MRE permission to open dialogs" },
+ payload.MessageId
+ );
+ onCompleteCallback?.Invoke();
+ }
else
{
MREAPI.AppsAPI.DialogFactory.ShowDialog(this, payload.Text, payload.AcceptInput, (submitted, text) =>
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/Core/Actor.cs b/MREUnityRuntime/MREUnityRuntimeLib/Core/Actor.cs
index d710aa45..8003c714 100644
--- a/MREUnityRuntime/MREUnityRuntimeLib/Core/Actor.cs
+++ b/MREUnityRuntime/MREUnityRuntimeLib/Core/Actor.cs
@@ -777,7 +777,8 @@ private bool PerformAttach()
DetachFromAttachPointParent();
IUserInfo userInfo = MREAPI.AppsAPI.UserInfoProvider.GetUserInfo(App, Attachment.UserId);
- if (userInfo != null)
+ if (userInfo != null &&
+ (Attachment.UserId != App.LocalUser?.Id || App.GrantedPermissions.HasFlag(Permissions.UserInteraction)))
{
userInfo.BeforeAvatarDestroyed -= UserInfo_BeforeAvatarDestroyed;
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/Core/Permissions.cs b/MREUnityRuntime/MREUnityRuntimeLib/Core/Permissions.cs
new file mode 100644
index 00000000..9f9a1fd8
--- /dev/null
+++ b/MREUnityRuntime/MREUnityRuntimeLib/Core/Permissions.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections.Generic;
+
+namespace MixedRealityExtension.Core
+{
+ [Flags]
+ public enum Permissions : long
+ {
+ None = 0,
+ Execution = 1,
+ UserTracking = 2,
+ UserInteraction = 4
+ }
+
+ public static class PermissionsExtensions
+ {
+ ///
+ /// Convenience method to convert an enumerable of permissions into a bitfield
+ ///
+ ///
+ ///
+ public static Permissions ToFlags(this IEnumerable enumerable)
+ {
+ var aggregate = Permissions.None;
+ foreach (var perm in enumerable)
+ {
+ aggregate |= perm;
+ }
+ return aggregate;
+ }
+
+ ///
+ /// Convenience method to convert a bitfield of Permissions into an enumerable
+ ///
+ ///
+ ///
+ public static IEnumerable ToEnumerable(this Permissions flags)
+ {
+ var allPerms = Enum.GetValues(typeof(Permissions));
+ var aggregate = new List(allPerms.Length);
+ foreach (Permissions perm in allPerms)
+ {
+ if (flags.HasFlag(perm))
+ {
+ aggregate.Add(perm);
+ }
+ }
+ return aggregate;
+ }
+ }
+}
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/Factories/SimplePermissionsManager.cs b/MREUnityRuntime/MREUnityRuntimeLib/Factories/SimplePermissionsManager.cs
new file mode 100644
index 00000000..e370b69b
--- /dev/null
+++ b/MREUnityRuntime/MREUnityRuntimeLib/Factories/SimplePermissionsManager.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using MixedRealityExtension.Core;
+using MixedRealityExtension.PluginInterfaces;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MixedRealityExtension.Factories
+{
+ ///
+ /// Simple permission manager that grants a fixed set of permissions to all MREs
+ ///
+ public class SimplePermissionManager : IPermissionManager
+ {
+ ///
+ /// The static set of permissions that this manager grants
+ ///
+ public Permissions GrantedPermissions { get; private set; }
+
+ ///
+ /// Set up the simple manager
+ ///
+ /// The permissions to grant to all MREs
+ public SimplePermissionManager(Permissions grantedPermissions)
+ {
+ GrantedPermissions = grantedPermissions;
+ }
+
+ ///
+ public event Action OnPermissionDecisionsChanged;
+
+ ///
+ public Task PromptForPermissions(
+ Uri appLocation,
+ IEnumerable permissionsNeeded,
+ IEnumerable permissionsWanted,
+ Permissions permissionFlagsNeeded,
+ Permissions permissionFlagsWanted,
+ AppManifest appManifest)
+ {
+ return Task.FromResult(GrantedPermissions);
+ }
+
+ ///
+ public Permissions CurrentPermissions(Uri appLocation)
+ {
+ return GrantedPermissions;
+ }
+ }
+}
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/MREUnityRuntimeLib.csproj b/MREUnityRuntime/MREUnityRuntimeLib/MREUnityRuntimeLib.csproj
index 5d8044c8..ea0b5d6e 100644
--- a/MREUnityRuntime/MREUnityRuntimeLib/MREUnityRuntimeLib.csproj
+++ b/MREUnityRuntime/MREUnityRuntimeLib/MREUnityRuntimeLib.csproj
@@ -111,6 +111,7 @@
+
@@ -125,6 +126,7 @@
+
@@ -136,6 +138,7 @@
+
@@ -150,6 +153,7 @@
+
diff --git a/MREUnityRuntime/MREUnityRuntimeLib/PluginInterfaces/IPermissionManager.cs b/MREUnityRuntime/MREUnityRuntimeLib/PluginInterfaces/IPermissionManager.cs
new file mode 100644
index 00000000..3d0ea408
--- /dev/null
+++ b/MREUnityRuntime/MREUnityRuntimeLib/PluginInterfaces/IPermissionManager.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using MixedRealityExtension.Core;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MixedRealityExtension.PluginInterfaces
+{
+ ///
+ /// Permission management interface for MRE host apps. Supports both IEnumerable and bitfield-based permission lists.
+ ///
+ public interface IPermissionManager
+ {
+ ///
+ /// Request permissions from the user, and return a Task that resolves with those permissions the user has granted.
+ ///
+ /// The URI of the MRE requesting permission.
+ /// An enumerable of the permissions required for the MRE to run.
+ /// An enumerable of the permissions the MRE can use, but are not required.
+ /// Same as permissionsNeeded, but in a bitfield.
+ /// Same as permissionsWanted, but in a bitfield.
+ /// The full app manifest, which includes enumerations of the required and optional permissions.
+ ///
+ Task PromptForPermissions(
+ Uri appLocation,
+ IEnumerable permissionsNeeded,
+ IEnumerable permissionsWanted,
+ Permissions permissionFlagsNeeded,
+ Permissions permissionFlagsWanted,
+ AppManifest appManifest);
+
+ ///
+ /// Get the currently granted permissions for the MRE origin without requesting new ones.
+ ///
+ /// The URI of the MRE that you want to know about.
+ /// A bitfield of the currently granted permissions for the given MRE.
+ Permissions CurrentPermissions(Uri appLocation);
+
+ ///
+ /// Event that is fired when any permissions are edited. Receives as arguments the app location URI, the old
+ /// permission set, and the new permission set.
+ ///
+ event Action OnPermissionDecisionsChanged;
+ }
+}