Initial checkin
Initial checkin of Quest App Launcher
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class AppEntry : MonoBehaviour
|
||||
{
|
||||
public string packageId;
|
||||
public string appName;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53f2e028e4291b741960616a445cf2ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,242 @@
|
||||
/************************************************************************************
|
||||
|
||||
Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
See SampleFramework license.txt for license terms. Unless required by applicable law
|
||||
or agreed to in writing, the sample code is provided “AS IS” WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the license for specific
|
||||
language governing permissions and limitations under the license.
|
||||
|
||||
************************************************************************************/
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.XR;
|
||||
using System;
|
||||
using Oculus.Platform;
|
||||
using TMPro;
|
||||
|
||||
namespace QuestAppLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// Usage: demonstrate how to use overlay layers for a paneled UI system
|
||||
/// On Mobile, we support both Cylinder layer and Quad layer
|
||||
/// Press any button: it will cycle [world geometry Quad]->[overlay layer Quad]->[world geometry cylinder]->[overlay layer cylinder]
|
||||
/// On PC, only Quad layer is supported
|
||||
/// Press any button: it will cycle [world geometry Quad]->[overlay layer Quad]
|
||||
///
|
||||
/// You should be able to observe sharper and less aliased image when switch from world geometry to overlay layer.
|
||||
///
|
||||
/// </summary>
|
||||
public class GridPopulation : MonoBehaviour
|
||||
{
|
||||
// File name of app name overrides
|
||||
const string AppNameOverrideFile = "appnames.txt";
|
||||
|
||||
// Extension search for icon overrides
|
||||
const string IconOverrideExtSearch = "*.jpg";
|
||||
|
||||
// Grid content game object
|
||||
public GameObject gridContent;
|
||||
|
||||
// App info GameObject (a cell in the grid content)
|
||||
public GameObject prefab;
|
||||
|
||||
#region MonoBehaviour handler
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Set high texture resolution scale to minimize aliasing
|
||||
XRSettings.eyeTextureResolutionScale = 2.0f;
|
||||
|
||||
// Initialize the core platform
|
||||
Core.AsyncInitialize();
|
||||
|
||||
// Populate the grid
|
||||
StartCoroutine(Populate());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
/// <summary>
|
||||
/// Static method for lauching an Android app
|
||||
/// </summary>
|
||||
/// <param name="packageId"></param>
|
||||
static public void LaunchApp(string packageId)
|
||||
{
|
||||
using (AndroidJavaClass up = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (AndroidJavaObject ca = up.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
using (AndroidJavaObject packageManager = ca.Call<AndroidJavaObject>("getPackageManager"))
|
||||
{
|
||||
AndroidJavaObject launchIntent = null;
|
||||
|
||||
try
|
||||
{
|
||||
launchIntent = packageManager.Call<AndroidJavaObject>("getLaunchIntentForPackage", packageId);
|
||||
ca.Call("startActivity", launchIntent);
|
||||
|
||||
// Quest doesn't like multiple VR apps running simultaneously. Kill ourselves.
|
||||
UnityEngine.Application.Quit();
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.Log(string.Format("Failed to launch app {0}: {1}", packageId, e.Message));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (null != launchIntent)
|
||||
{
|
||||
launchIntent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the grid from installed apps
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator Populate()
|
||||
{
|
||||
var persistentDataPath = UnityEngine.Application.persistentDataPath;
|
||||
Debug.Log("Persistent data path: " + persistentDataPath);
|
||||
|
||||
using (AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
// Dictionary to hold package name -> app index, app name
|
||||
var packageNameToAppName = new Dictionary<string, (int Index, string AppName)>();
|
||||
|
||||
// Get # of installed apps
|
||||
int numApps = currentActivity.Call<int>("getSize");
|
||||
Debug.Log("# installed apps: " + numApps);
|
||||
|
||||
// Get current package name (to filter this out))
|
||||
var currentPackageName = currentActivity.Call<string>("getPackageName");
|
||||
|
||||
// Get installed package and app names
|
||||
for (int i = 0; i < numApps; i++)
|
||||
{
|
||||
var packageName = currentActivity.Call<string>("getPackageName", i);
|
||||
var appName = currentActivity.Call<string>("getAppName", i);
|
||||
|
||||
if (packageName.Equals(currentPackageName))
|
||||
{
|
||||
// Skip current package
|
||||
continue;
|
||||
}
|
||||
|
||||
packageNameToAppName.Add(packageName, (i, appName));
|
||||
Debug.Log("[" + i + "] package: " + packageName + ", name: " + appName);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
|
||||
// Override app names, if any
|
||||
// This is just a file with comma-separated packageName,appName pairs
|
||||
var appNameOverrideFilePath = Path.Combine(persistentDataPath, AppNameOverrideFile);
|
||||
if (File.Exists(appNameOverrideFilePath))
|
||||
{
|
||||
Debug.Log("Found file: " + appNameOverrideFilePath);
|
||||
string[] overriddenNames = File.ReadAllLines(appNameOverrideFilePath);
|
||||
foreach (string overriddenName in overriddenNames)
|
||||
{
|
||||
var entry = overriddenName.Split(',');
|
||||
if (2 == entry.Length && packageNameToAppName.ContainsKey(entry[0]))
|
||||
{
|
||||
packageNameToAppName[entry[0]] = (packageNameToAppName[entry[0]].Index, entry[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Did not find: " + appNameOverrideFilePath);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
|
||||
// Load list of app icon overrides
|
||||
// This is a list of jpg images stored as packageName.jpg.
|
||||
var iconOverridePath = persistentDataPath;
|
||||
var iconOverrides = new Dictionary<string, string>();
|
||||
if (Directory.Exists(iconOverridePath))
|
||||
{
|
||||
foreach (var iconFileName in Directory.GetFiles(iconOverridePath, IconOverrideExtSearch))
|
||||
{
|
||||
var entry = Path.GetFileNameWithoutExtension(iconFileName);
|
||||
if (packageNameToAppName.ContainsKey(entry))
|
||||
{
|
||||
Debug.Log("Found file: " + iconFileName);
|
||||
iconOverrides[entry] = Path.Combine(iconOverridePath, iconFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
|
||||
// Populate grid with app information (name & icon)
|
||||
// Sort by app name
|
||||
foreach (var app in packageNameToAppName.OrderBy(key => key.Value.AppName))
|
||||
{
|
||||
// Create new instances of our app info prefab
|
||||
var newObj = (GameObject)Instantiate(prefab, gridContent.transform);
|
||||
|
||||
// Set app entry info
|
||||
var appEntry = newObj.GetComponent("AppEntry") as AppEntry;
|
||||
appEntry.packageId = app.Key;
|
||||
appEntry.appName = app.Value.AppName;
|
||||
|
||||
// Get app icon
|
||||
byte[] bytesIcon = null;
|
||||
bool useApkIcon = true;
|
||||
if (iconOverrides.ContainsKey(app.Key))
|
||||
{
|
||||
// Use overridden icon
|
||||
try
|
||||
{
|
||||
bytesIcon = File.ReadAllBytes(iconOverrides[app.Key]);
|
||||
useApkIcon = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
// Fall back to using the apk icon
|
||||
Debug.Log(string.Format("Error reading app icon from file [{0}]: {1}", iconOverrides[app.Key], e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
if (useApkIcon)
|
||||
{
|
||||
// Use built-in icon from the apk
|
||||
bytesIcon = (byte[])(Array)currentActivity.Call<sbyte[]>("getIcon", app.Value.Index);
|
||||
}
|
||||
|
||||
// Set the icon image
|
||||
var image = newObj.transform.Find("AppIcon").GetComponentInChildren<Image>();
|
||||
var texture = new Texture2D(2, 2, TextureFormat.RGB24, false);
|
||||
texture.filterMode = FilterMode.Trilinear;
|
||||
texture.anisoLevel = 16;
|
||||
//texture.Resize(144, 100, TextureFormat.RGB24, false);
|
||||
texture.LoadImage(bytesIcon);
|
||||
var rect = new Rect(0, 0, texture.width, texture.height);
|
||||
image.sprite = Sprite.Create(texture, rect, new Vector2(0.5f, 0.5f));
|
||||
|
||||
// Set app name in text
|
||||
var text = newObj.transform.Find("AppName").GetComponentInChildren<TextMeshProUGUI>();
|
||||
text.text = app.Value.AppName;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d75ad87b4f70d345becda5841f29b7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class OnInteraction : MonoBehaviour
|
||||
{
|
||||
protected Material oldHoverMat;
|
||||
public Material yellowMat;
|
||||
public Material backIdle;
|
||||
public Material backACtive;
|
||||
public UnityEngine.UI.Text outText;
|
||||
|
||||
public void OnHoverEnter(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry)
|
||||
{
|
||||
// Enable border
|
||||
EnableBorder(t, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnHoverExit(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry)
|
||||
{
|
||||
// Disable border
|
||||
EnableBorder(t, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSelected(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry)
|
||||
{
|
||||
// Launch app
|
||||
Debug.Log("Launching: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
|
||||
QuestAppLauncher.GridPopulation.LaunchApp(appEntry.packageId);
|
||||
}
|
||||
}
|
||||
|
||||
void EnableBorder(Transform t, bool enable)
|
||||
{
|
||||
var border = t.Find("Border");
|
||||
border?.gameObject.SetActive(enable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 054c7813b6beaab439c025873fec9b94
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
public class ScrollRectOverride : ScrollRect, IMoveHandler, IPointerClickHandler, IScrollHandler
|
||||
{
|
||||
private const float speedMultiplier = 15f;
|
||||
private float cellHeight = 0f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
this.cellHeight = this.transform.GetComponentInChildren<GridLayoutGroup>().cellSize.y;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Get vector from either left or right thumbstick
|
||||
var moveVector = OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick);
|
||||
if (moveVector.x == 0 && moveVector.y == 0)
|
||||
{
|
||||
moveVector = OVRInput.Get(OVRInput.Axis2D.SecondaryThumbstick);
|
||||
}
|
||||
|
||||
// Scroll by a fixed amount proportional to thumbstick position on each frame
|
||||
// and map this to a fraction of the total viewport size:
|
||||
// moveVector.y: The thumbstick vertical position normalized to [-1,1].
|
||||
// Time.deltaTime: The time delta since last frame
|
||||
// speedMultiplier: Just a multiplier to get a good scrolling speed.
|
||||
// So, moveVector.y * Time.deltaTime * speedMultiplier = the amount to scroll in "units"
|
||||
// proportional to thumbstick position since last frame.
|
||||
// this.cellHeight / this.content.sizeDelta.y = cell height / total content height.
|
||||
float verticalIncrement = moveVector.y * Time.deltaTime * speedMultiplier * this.cellHeight / this.content.sizeDelta.y;
|
||||
this.verticalNormalizedPosition = Mathf.Clamp01(this.verticalNormalizedPosition + verticalIncrement);
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData e)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
void IMoveHandler.OnMove(AxisEventData e)
|
||||
{
|
||||
}
|
||||
|
||||
void IScrollHandler.OnScroll(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
void OnMouseDrag()
|
||||
{
|
||||
}
|
||||
|
||||
void OnMouseUp()
|
||||
{
|
||||
}
|
||||
|
||||
void OnMouseDown()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c51693ac45aa2d4480f322a70ef9a67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user