Support for renaming apps

- Adds support for renaming apps: By pressing A or X on the controller, you can choose an alternate app name / icon. You can reset these changes back to default in Settings.
This commit is contained in:
tverona1
2019-09-08 14:28:45 -07:00
parent f6b9942b3a
commit 6bca1d0c07
17 changed files with 3134 additions and 81 deletions
+78 -2
View File
@@ -23,6 +23,7 @@ import java.util.Calendar;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import android.os.Bundle;
import android.util.Log;
import android.graphics.Bitmap;
@@ -152,7 +153,6 @@ public class AppInfo extends UnityPlayerActivity {
}
public static void unzip(String zipFileName, String targetPath) {
File outDir = new File(targetPath);
// Create target path if not exist
@@ -181,9 +181,85 @@ public class AppInfo extends UnityPlayerActivity {
}
}
}
stream.closeEntry();
}
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
public static void addFileToZip(String zipFilePath, String sourceFilePath, String entryName)
{
Log.v(TAG, "Adding to zip: " + sourceFilePath + " to " + zipFilePath + " with entry name " + entryName);
File zipFile = new File(zipFilePath);
File tempZipFile;
byte[] buffer = new byte[8192];
try {
// Create temporary zip file in same location as zip file
tempZipFile = File.createTempFile(zipFile.getName(), null, new File(zipFile.getParent()));
} catch (Exception e) {
e.printStackTrace();
return;
}
try (FileOutputStream fos = new FileOutputStream(tempZipFile);
BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
ZipOutputStream zos = new ZipOutputStream(bos)) {
if (zipFile.exists()) {
try (FileInputStream fis = new FileInputStream(zipFilePath);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zin = new ZipInputStream(bis)) {
// Copy contents of input zip file to temp zip file
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
if (ze.getName().equalsIgnoreCase(entryName)) {
// The file we're trying to add already exists, so skip it
continue;
}
zos.putNextEntry(ze);
int len;
while ((len = zin.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
}
}
}
// Add our new file
zos.putNextEntry(new ZipEntry(entryName));
try (FileInputStream fileFis = new FileInputStream(sourceFilePath);
BufferedInputStream fileBis = new BufferedInputStream(fileFis)) {
int len;
while ((len = fileBis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
}
zos.closeEntry();
zos.finish();
zos.close();
bos.close();
fos.close();
// Copy temp file to original zip file
if (zipFile.exists()) {
zipFile.delete();
}
if (!tempZipFile.renameTo(zipFile)) {
throw new Exception("Could not rename file " + tempZipFile.getName() + " to " + zipFile.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (tempZipFile.exists()) {
tempZipFile.delete();
}
}
}
+1 -1
View File
@@ -12,6 +12,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 20553fac56ec59645857c0732b787431, type: 3}
m_Name: OVRBuildConfig
m_EditorClassIdentifier:
androidSDKPath:
androidSDKPath: C:\Users\tvero\AppData\Local\Android\Sdk
gradlePath:
jdkPath:
File diff suppressed because it is too large Load Diff
+3
View File
@@ -8,6 +8,9 @@ public class AppEntry : MonoBehaviour
public string packageId;
public string appName;
public string externalIconPath;
public int installedApkIndex;
public bool isRenameMode;
// Start is called before the first frame update
void Start()
+93 -21
View File
@@ -16,7 +16,7 @@ namespace QuestAppLauncher
/// </summary>
public class ProcessedApp
{
public int Index;
public int Index = -1;
public string PackageName;
public string AppName;
public string AutoTabName;
@@ -32,7 +32,7 @@ namespace QuestAppLauncher
/// Class used to deserialize appnames.json entries
/// </summary>
[Serializable]
class JsonAppNamesEntry
public class JsonAppNamesEntry
{
public string Name;
public string Category;
@@ -43,6 +43,10 @@ namespace QuestAppLauncher
const string AppNameOverrideTxtFileSearch = "appnames*.txt";
const string AppNameOverrideJsonFileSearch = "appnames*.json";
// Rename file names
public const string RenameJsonFileName = "appnames_rename.json";
public const string RenameIconPackFileName = "iconpack_rename.zip";
// Icon pack search string
const string IconPackSearch = "iconpack*.zip";
@@ -66,7 +70,14 @@ namespace QuestAppLauncher
public static readonly string[] Auto_Tabs = { Tab_Quest, Tab_Go, Tab_2D };
public static Dictionary<string, ProcessedApp> ProcessApps(Config config)
/// <summary>
/// Entry point for app processing: Applies app name overrides (from appnames.txt/json) and app icons (from individual jpgs or icon packs).
/// Handles extraction of icon packs if zip file modified. Returns a list of processed apps.
/// </summary>
/// <param name="config">Application config</param>
/// <param name="isRenameMode">Whether in rename mode. If so, returns all apps found, not just installed ones.</param>
/// <returns>Dictionary of processed apps</returns>
public static Dictionary<string, ProcessedApp> ProcessApps(Config config, bool isRenameMode = false)
{
var persistentDataPath = UnityEngine.Application.persistentDataPath;
Debug.Log("Persistent data path: " + persistentDataPath);
@@ -78,7 +89,6 @@ namespace QuestAppLauncher
using (AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity"))
{
// Get # of installed apps
int numApps = currentActivity.Call<int>("getSize");
Debug.Log("# installed apps: " + numApps);
@@ -91,12 +101,16 @@ namespace QuestAppLauncher
processedLastTimeUsed = true;
}
// Add current package name to excludedPackageNames to filter it out
excludedPackageNames.Add(currentActivity.Call<string>("getPackageName"));
if (!isRenameMode)
{
// Add current package name to excludedPackageNames to filter it out
excludedPackageNames.Add(currentActivity.Call<string>("getPackageName"));
}
// This is a file containing packageNames that will be excluded
// This is a file containing packageNames that will be excluded.
// Skip this if in rename mode.
var excludedPackageNamesFilePath = Path.Combine(persistentDataPath, ExcludedPackagesFile);
if (File.Exists(excludedPackageNamesFilePath))
if (!isRenameMode && File.Exists(excludedPackageNamesFilePath))
{
Debug.Log("Found file: " + excludedPackageNamesFilePath);
string[] excludedPackages = File.ReadAllLines(excludedPackageNamesFilePath);
@@ -151,16 +165,16 @@ namespace QuestAppLauncher
}
// Process app name overrides files (both downloaded & manually created)
ProcessAppNameOverrideFiles(apps, AssetsDownloader.GetOrCreateDownloadPath());
ProcessAppNameOverrideFiles(apps, persistentDataPath);
ProcessAppNameOverrideFiles(isRenameMode, apps, AssetsDownloader.GetOrCreateDownloadPath());
ProcessAppNameOverrideFiles(isRenameMode, apps, persistentDataPath);
// Extract icon packs (both downloaded & manually created)
ExtractIconPacks(currentActivity, AssetsDownloader.GetOrCreateDownloadPath());
ExtractIconPacks(currentActivity, persistentDataPath);
// Process extracted icons (both downloaded & manually created)
ProcessExtractedIcons(apps, AssetsDownloader.GetOrCreateDownloadPath());
ProcessExtractedIcons(apps, persistentDataPath);
ProcessExtractedIcons(isRenameMode, apps, AssetsDownloader.GetOrCreateDownloadPath());
ProcessExtractedIcons(isRenameMode, apps, persistentDataPath);
// Process any individual icons
var iconOverridePath = persistentDataPath;
@@ -173,25 +187,32 @@ namespace QuestAppLauncher
return apps;
}
private static void ProcessAppNameOverrideFiles(Dictionary<string, ProcessedApp> apps, string path)
private static void ProcessAppNameOverrideFiles(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string path)
{
// Process appname*.json files, sorted by name
foreach (var filePath in Directory.GetFiles(
path, AppNameOverrideJsonFileSearch).OrderBy(f => f))
{
ProcessAppNameOverrideJsonFile(apps, filePath);
ProcessAppNameOverrideJsonFile(isRenameMode, apps, filePath);
}
// Process appname*.txt files, sorted by name
foreach (var filePath in Directory.GetFiles(
path, AppNameOverrideTxtFileSearch).OrderBy(f => f))
{
ProcessAppNameOverrideTxtFile(apps, filePath);
ProcessAppNameOverrideTxtFile(isRenameMode, apps, filePath);
}
}
private static void ProcessAppNameOverrideJsonFile(Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
private static void ProcessAppNameOverrideJsonFile(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
{
if (isRenameMode && appNameOverrideFilePath.Equals(Path.Combine(UnityEngine.Application.persistentDataPath, RenameJsonFileName),
StringComparison.InvariantCultureIgnoreCase))
{
// In rename mode, so skip the rename json file itself
return;
}
// Override app names, if any
Debug.Log("Found file: " + appNameOverrideFilePath);
@@ -207,7 +228,17 @@ namespace QuestAppLauncher
if (!apps.ContainsKey(packageName))
{
// App is not installed, so skip
// App is not installed
if (isRenameMode)
{
// If rename mode, just add it
apps[packageName] = new ProcessedApp
{
PackageName = packageName,
AppName = appName,
};
}
continue;
}
@@ -264,7 +295,7 @@ namespace QuestAppLauncher
}
}
private static void ProcessAppNameOverrideTxtFile(Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
private static void ProcessAppNameOverrideTxtFile(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
{
// Override app names, if any
// This is just a file with comma-separated packageName,appName[,category1[, category2]]
@@ -295,7 +326,17 @@ namespace QuestAppLauncher
if (!apps.ContainsKey(packageName))
{
// App is not installed, so skip
// App is not installed
if (isRenameMode)
{
// If rename mode, so just add it
apps[packageName] = new ProcessedApp
{
PackageName = packageName,
AppName = appName,
};
}
continue;
}
@@ -401,7 +442,7 @@ namespace QuestAppLauncher
}
}
private static void ProcessExtractedIcons(Dictionary<string, ProcessedApp> apps, string iconsPath)
private static void ProcessExtractedIcons(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string iconsPath)
{
// Full path of extraction dir
var extractionDirPath = Path.Combine(iconsPath, IconPackExtractionDir);
@@ -412,6 +453,13 @@ namespace QuestAppLauncher
var dirs = Directory.GetDirectories(extractionDirPath).OrderBy(f => f);
foreach (var dir in dirs)
{
if (isRenameMode && dir.StartsWith(Path.Combine(UnityEngine.Application.persistentDataPath, RenameIconPackFileName),
StringComparison.InvariantCultureIgnoreCase))
{
// In rename mode, so skip the extracted rename icon pack itself
continue;
}
ProcessIconsInPath(apps, dir);
}
}
@@ -449,7 +497,7 @@ namespace QuestAppLauncher
catch (Exception e)
{
// Fall back to using the apk icon
Debug.Log(string.Format("Error reading app icon from file [{0}]: {1}", iconPath, e.Message));
Debug.LogFormat("Error reading app icon from file [{0}]: {1}", iconPath, e.Message);
}
}
@@ -500,6 +548,30 @@ namespace QuestAppLauncher
return false;
}
/// <summary>
/// Static method to delete the rename json and icon pack
/// </summary>
/// <returns>true if file exists</returns>
static public bool DeleteRenameFiles()
{
var ret = false;
var renameJsonFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, RenameJsonFileName);
if (File.Exists(renameJsonFilePath))
{
File.Delete(renameJsonFilePath);
ret = true;
}
var renameIconPackFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, RenameIconPackFileName);
if (File.Exists(renameIconPackFilePath))
{
File.Delete(renameIconPackFilePath);
ret = true;
}
return ret;
}
/// <summary>
/// Static method for launching an Android app
/// </summary>
+1 -1
View File
@@ -105,7 +105,7 @@ namespace QuestAppLauncher
{
// We downloaded new assets, so re-load the scene
Debug.Log("Downloaded new assets. Re-populating panel");
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
}
});
}
-1
View File
@@ -137,7 +137,6 @@ namespace QuestAppLauncher
{
Debug.Log(string.Format("Failed to write config: {0}", e.Message));
}
}
}
}
+65 -15
View File
@@ -44,6 +44,10 @@ namespace QuestAppLauncher
public GameObject leftTabContainerContent;
public GameObject rightTabContainerContent;
// Rename panel game objects
public GameObject renamePanelContainer;
public GameObject scrollRenameContainer;
// Tracking space
public GameObject trackingSpace;
@@ -78,13 +82,21 @@ namespace QuestAppLauncher
}
#endregion
#region Private Functions
/// <summary>
/// Populates rename grid
/// </summary>
/// <returns></returns>
public async Task PopulateRenameAsync()
{
PopulateAsync(true);
}
#region Private Functions
/// <summary>
/// Populate the grid from installed apps
/// </summary>
/// <returns></returns>
private async Task PopulateAsync()
private async Task PopulateAsync(bool isRenameMode = false)
{
// Load configuration
var config = ConfigPersistence.LoadConfig();
@@ -96,7 +108,7 @@ namespace QuestAppLauncher
try
{
return AppProcessor.ProcessApps(config);
return AppProcessor.ProcessApps(config, isRenameMode);
}
finally
{
@@ -105,14 +117,21 @@ namespace QuestAppLauncher
});
// Download updates in the background
if (config.autoUpdate && !GlobalState.Instance.CheckedForUpdate)
if (!isRenameMode && config.autoUpdate && !GlobalState.Instance.CheckedForUpdate)
{
GlobalState.Instance.CheckedForUpdate = true;
AssetsDownloader.DownloadAssetsAsync(config, this.downloadStatusIndicator);
}
// Populate the panel content
await PopulatePanelContentAsync(config, apps);
if (!isRenameMode)
{
await PopulatePanelContentAsync(config, apps);
}
else
{
await PopulateRenamePanelContentAsync(config, apps);
}
}
private async Task PopulatePanelContentAsync(
@@ -168,14 +187,14 @@ namespace QuestAppLauncher
// Process the tab containers
var gridContents = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
ProcessTabContainer(config, topTabs, this.topTabContainer, this.topTabContainerContent, true, gridContents);
ProcessTabContainer(config, leftTabs, this.leftTabContainer, this.leftTabContainerContent, false, gridContents);
ProcessTabContainer(config, rightTabs, this.rightTabContainer, this.rightTabContainerContent, false, gridContents);
ProcessTabContainer(config, topTabs, this.panelContainer, this.scrollContainer, this.topTabContainer, this.topTabContainerContent, true, gridContents);
ProcessTabContainer(config, leftTabs, this.panelContainer, this.scrollContainer, this.leftTabContainer, this.leftTabContainerContent, false, gridContents);
ProcessTabContainer(config, rightTabs, this.panelContainer, this.scrollContainer, this.rightTabContainer, this.rightTabContainerContent, false, gridContents);
// Set panel size, use any grid content for reference (since they are all the same size)
if (gridContents.Count > 0)
{
var size = SetGridSize(gridContents.First().Value, config.gridSize.rows, config.gridSize.cols);
var size = SetGridSize(this.panelContainer, gridContents.First().Value, config.gridSize.rows, config.gridSize.cols);
// Adjust tab sizes
ResizeTabContent(this.topTabContainer.transform, size, topTabs.Count, true);
@@ -210,6 +229,28 @@ namespace QuestAppLauncher
}
}
private async Task PopulateRenamePanelContentAsync(
Config config, Dictionary<string, ProcessedApp> apps)
{
// Create scroll view
var scrollView = (GameObject)Instantiate(this.prefabScrollView, this.scrollRenameContainer.transform);
scrollView.SetActive(true);
var scrollRectOverride = scrollView.GetComponent<ScrollRectOverride>();
scrollRectOverride.trackingSpace = this.trackingSpace.transform;
var gridContent = scrollRectOverride.content.gameObject;
// Set panel size
SetGridSize(this.renamePanelContainer, gridContent, config.gridSize.rows, config.gridSize.cols);
// Populate grid with app information (name & icon)
// Sort alphabetically
foreach (var app in apps.OrderBy(key => key.Value.AppName, StringComparer.InvariantCultureIgnoreCase))
{
await AddCellToGridAsync(app.Value, gridContent.transform, true);
}
}
private void ResizeTabContent(Transform transform, Vector2 size, int childCount, bool isHorizontal)
{
var rect = transform.GetComponent<RectTransform>();
@@ -235,7 +276,7 @@ namespace QuestAppLauncher
transform.gameObject.GetComponent<ScrollButtonHandler>().RefreshScrollContent(childCount);
}
private Vector2 SetGridSize(GameObject gridContent, int rows, int cols)
private Vector2 SetGridSize(GameObject panel, GameObject gridContent, int rows, int cols)
{
// Make sure grid size have sane value
cols = Math.Min(cols, 10);
@@ -264,7 +305,7 @@ namespace QuestAppLauncher
// Adjust grid container rect transform
var size = new Vector2(width, height);
var gridTransform = this.panelContainer.GetComponent<RectTransform>();
var gridTransform = panel.GetComponent<RectTransform>();
gridTransform.sizeDelta = size;
// Adjust grid container Y position to maintain constant height.
@@ -277,7 +318,7 @@ namespace QuestAppLauncher
return size;
}
private void ProcessTabContainer(Config config, List<string> tabs, GameObject tabContainer,
private void ProcessTabContainer(Config config, List<string> tabs, GameObject panel, GameObject scroll, GameObject tabContainer,
GameObject tabContainerContent, bool setFirstTabActive, Dictionary<string, GameObject> gridContents)
{
// Create scroll views and tabs
@@ -286,7 +327,7 @@ namespace QuestAppLauncher
Debug.LogFormat("Populating tab '{0}'", tabName);
// Create scroll view
var scrollView = (GameObject)Instantiate(this.prefabScrollView, this.scrollContainer.transform);
var scrollView = (GameObject)Instantiate(this.prefabScrollView, scroll.transform);
var scrollRectOverride = scrollView.GetComponent<ScrollRectOverride>();
scrollRectOverride.trackingSpace = this.trackingSpace.transform;
@@ -299,7 +340,7 @@ namespace QuestAppLauncher
var toggle = tab.GetComponent<Toggle>();
toggle.isOn = setFirstTabActive;
toggle.group = this.panelContainer.GetComponent<ToggleGroup>();
toggle.group = panel.GetComponent<ToggleGroup>();
toggle.onValueChanged.AddListener(scrollView.SetActive);
setFirstTabActive = false;
@@ -309,8 +350,14 @@ namespace QuestAppLauncher
}
}
private async Task AddCellToGridAsync(ProcessedApp app, Transform transform)
private async Task AddCellToGridAsync(ProcessedApp app, Transform transform, bool isRenameMode = false)
{
if (app.Index == -1 && string.IsNullOrEmpty(app.IconPath))
{
// If we have neither app index or icon path, skip this
return;
}
// Get app icon in background
var bytesIcon = await Task.Run(() =>
{
@@ -333,6 +380,9 @@ namespace QuestAppLauncher
var appEntry = newObj.GetComponent("AppEntry") as AppEntry;
appEntry.packageId = app.PackageName;
appEntry.appName = app.AppName;
appEntry.isRenameMode = isRenameMode;
appEntry.installedApkIndex = app.Index;
appEntry.externalIconPath = app.IconPath;
// Set the icon image
if (null != bytesIcon)
+40
View File
@@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace QuestAppLauncher
{
public class HideAppHandler : MonoBehaviour
{
public TextMeshProUGUI text;
private AppEntry appEntryToHide;
public void OnHideApp(AppEntry appEntryToHide)
{
this.appEntryToHide = appEntryToHide;
// Set dialog text
this.text.text = string.Format("Hide app '{0}'?", appEntryToHide.appName);
// Launch dialog
this.gameObject.SetActive(true);
}
public async void OnOk()
{
// Add package name to excluded file
Debug.Log("Hiding: " + this.appEntryToHide.appName + " (package id: " + this.appEntryToHide.packageId + ")");
AppProcessor.AddAppToExcludedFile(this.appEntryToHide.packageId);
// Reload the scene - this will force all the tabs to update
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
}
public void OnCancel()
{
this.gameObject.SetActive(false);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f9275ecafce3994394538da0abe4977
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,147 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using UnityEngine;
namespace QuestAppLauncher
{
public class AsyncCoroutineRunner : MonoBehaviour
{
static AsyncCoroutineRunner _instance;
public static AsyncCoroutineRunner Instance
{
get
{
if (_instance == null)
{
_instance = new GameObject("AsyncCoroutineRunner")
.AddComponent<AsyncCoroutineRunner>();
}
return _instance;
}
}
void Awake()
{
// Don't show in scene hierarchy
gameObject.hideFlags = HideFlags.HideAndDontSave;
DontDestroyOnLoad(gameObject);
}
}
public static class IEnumeratorAwaitExtensions
{
public static SimpleCoroutineAwaiter<AsyncOperation> GetAwaiter(this AsyncOperation instruction)
{
return GetAwaiterReturnSelf(instruction);
}
static SimpleCoroutineAwaiter<T> GetAwaiterReturnSelf<T>(T instruction)
{
var awaiter = new SimpleCoroutineAwaiter<T>();
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
InstructionWrappers.ReturnSelf(awaiter, instruction)));
return awaiter;
}
static void RunOnUnityScheduler(Action action)
{
if (SynchronizationContext.Current == SyncContextUtil.UnitySynchronizationContext)
{
action();
}
else
{
SyncContextUtil.UnitySynchronizationContext.Post(_ => action(), null);
}
}
public class SimpleCoroutineAwaiter<T> : INotifyCompletion
{
bool _isDone;
Exception _exception;
Action _continuation;
T _result;
public bool IsCompleted
{
get { return _isDone; }
}
public T GetResult()
{
Debug.Assert(_isDone);
if (_exception != null)
{
ExceptionDispatchInfo.Capture(_exception).Throw();
}
return _result;
}
public void Complete(T result, Exception e)
{
Debug.Assert(!_isDone);
_isDone = true;
_exception = e;
_result = result;
// Always trigger the continuation on the unity thread when awaiting on unity yield
// instructions
if (_continuation != null)
{
RunOnUnityScheduler(_continuation);
}
}
void INotifyCompletion.OnCompleted(Action continuation)
{
Debug.Assert(_continuation == null);
Debug.Assert(!_isDone);
_continuation = continuation;
}
}
static class InstructionWrappers
{
public static IEnumerator ReturnSelf<T>(
SimpleCoroutineAwaiter<T> awaiter, T instruction)
{
yield return instruction;
awaiter.Complete(instruction, null);
}
}
public static class SyncContextUtil
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void Install()
{
UnitySynchronizationContext = SynchronizationContext.Current;
UnityThreadId = Thread.CurrentThread.ManagedThreadId;
}
public static int UnityThreadId
{
get; private set;
}
public static SynchronizationContext UnitySynchronizationContext
{
get; private set;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e90d336a3fcd1ce46bda7a08309b4e83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+39 -15
View File
@@ -1,16 +1,17 @@
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Threading.Tasks;
namespace QuestAppLauncher
{
public class OnInteraction : MonoBehaviour
{
protected Material oldHoverMat;
public Material yellowMat;
public Material backIdle;
public Material backACtive;
public UnityEngine.UI.Text outText;
// Hide app handler
public HideAppHandler hideAppHandler;
// Rename app handler
public RenameHandler renameHandler;
public void OnHoverEnter(Transform t)
{
@@ -32,28 +33,51 @@ namespace QuestAppLauncher
}
}
public void OnSelected(Transform t)
public async 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 + ")");
AppProcessor.LaunchApp(appEntry.packageId);
if (appEntry.isRenameMode)
{
this.renameHandler.Rename(appEntry);
}
else
{
await Task.Run(() =>
{
AndroidJNI.AttachCurrentThread();
try
{
// Launch app
Debug.Log("Launching: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
AppProcessor.LaunchApp(appEntry.packageId);
}
finally
{
AndroidJNI.DetachCurrentThread();
}
});
}
}
}
public void OnSelectedPressedBorY(Transform t)
{
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
if (null != appEntry)
if (null != appEntry && !appEntry.isRenameMode)
{
// Add package name to excluded file
Debug.Log("Hiding: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
AppProcessor.AddAppToExcludedFile(appEntry.packageId);
this.hideAppHandler.OnHideApp(appEntry);
}
}
// Remove ourselves from the gridview
Destroy(t.gameObject);
public void OnSelectedPressedAorX(Transform t)
{
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
if (null != appEntry && !appEntry.isRenameMode)
{
this.renameHandler.OpenRenamePanel(appEntry);
}
}
+167
View File
@@ -0,0 +1,167 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace QuestAppLauncher
{
public class RenameHandler : MonoBehaviour
{
// Referenced gameobjects
public GameObject panelContainer;
public GameObject closeRenameButton;
public GameObject renamePanelContainer;
public GameObject renameScrollContainer;
public TextMeshProUGUI renameLabel;
public GridPopulation gridPopulation;
// Whether the rename grid has already been populated
private bool isPopulated = false;
// App entry of the app to rename
private AppEntry appToRename;
/// <summary>
/// Opens the rename panel.
/// </summary>
/// <param name="appToRename">App to rename</param>
public async void OpenRenamePanel(AppEntry appToRename)
{
Debug.Log("Open Rename Panel");
this.panelContainer.SetActive(false);
this.closeRenameButton.SetActive(true);
this.renamePanelContainer.SetActive(true);
this.appToRename = appToRename;
this.renameLabel.text = string.Format("Pick entry to rename app '{0}' (package '{1}')", appToRename.appName, appToRename.packageId);
// Only populate once.
// This will be reset in the next scene load (by design, since we may download new assets).
if (!this.isPopulated)
{
await gridPopulation.PopulateRenameAsync();
this.isPopulated = true;
}
}
/// <summary>
/// Opens the rename dialog
/// </summary>
/// <param name="entry"></param>
public async void Rename(AppEntry appTarget)
{
Debug.Assert(null != this.appToRename, "App to rename is null");
var filePath = appTarget.externalIconPath;
bool cleanupFile = false;
// If icon path is null, extract icon from apk
if (null == filePath)
{
filePath = Path.Combine(AssetsDownloader.GetOrCreateDownloadPath(), this.appToRename.packageId + ".jpg");
cleanupFile = true;
var bytes = appTarget.sprite.GetComponent<Image>().sprite.texture.EncodeToJPG();
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await fileStream.WriteAsync(bytes, 0, bytes.Length);
};
}
await Task.Run(() =>
{
AndroidJNI.AttachCurrentThread();
try
{
// Add to json file
AddToRenameJsonFile(this.appToRename.packageId, appTarget.appName);
// Add icon to zip
using (AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity"))
{
var renameIconPackFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, AppProcessor.RenameIconPackFileName);
currentActivity.CallStatic("addFileToZip", renameIconPackFilePath, filePath, this.appToRename.packageId + ".jpg");
}
}
finally
{
AndroidJNI.DetachCurrentThread();
}
});
if (cleanupFile && File.Exists(filePath))
{
File.Delete(filePath);
}
// Reload the scene
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
}
/// <summary>
/// Closes the rename panel
/// </summary>
public void CloseRenamePanel()
{
Debug.Log("Close Rename Panel");
this.panelContainer.SetActive(true);
this.closeRenameButton.SetActive(false);
this.renamePanelContainer.SetActive(false);
}
/// <summary>
/// Adds app to rename to json file
/// </summary>
/// <param name="packageId">Package id of app</param>
/// <param name="appName">App name</param>
private void AddToRenameJsonFile(string packageId, string appName)
{
var renameJsonFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, AppProcessor.RenameJsonFileName);
Dictionary<string, AppProcessor.JsonAppNamesEntry> jsonAppNames = null;
if (File.Exists(renameJsonFilePath))
{
// Read rename json file
try
{
var json = File.ReadAllText(renameJsonFilePath, Encoding.UTF8);
jsonAppNames = JsonConvert.DeserializeObject<Dictionary<string, AppProcessor.JsonAppNamesEntry>>(json);
}
catch (Exception e)
{
// On exception, we'll keep going & just overwrite existing file contents
Debug.LogFormat("Failed to process json rename app file: {0}", e.Message);
}
}
// Add entry
if (null == jsonAppNames)
{
jsonAppNames = new Dictionary<string, AppProcessor.JsonAppNamesEntry>();
}
jsonAppNames[packageId] = new AppProcessor.JsonAppNamesEntry { Name = appName };
// Persist
try
{
File.WriteAllText(renameJsonFilePath, JsonConvert.SerializeObject(jsonAppNames, Formatting.Indented));
}
catch (Exception e)
{
Debug.Log(string.Format("Failed to write json rename app file: {0}", e.Message));
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f0617ad70a22344684e254e37ddef2e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+17 -3
View File
@@ -39,6 +39,7 @@ namespace QuestAppLauncher
public GameObject usageStatsPermText;
private bool deletedHiddenAppsFile = false;
private bool deletedRenameFiles = false;
private Config config = null;
@@ -50,6 +51,9 @@ namespace QuestAppLauncher
this.closeSettingsButton.SetActive(true);
this.settingsContainer.SetActive(true);
this.deletedHiddenAppsFile = false;
this.deletedRenameFiles = false;
// Load config
this.config = ConfigPersistence.LoadConfig();
@@ -138,6 +142,16 @@ namespace QuestAppLauncher
}
}
public void DeleteRenameFiles()
{
Debug.Log("Delete Rename files");
if (!this.deletedRenameFiles)
{
this.deletedRenameFiles = AppProcessor.DeleteRenameFiles();
}
}
public void UpdateGridColText()
{
var cols = gridCols.GetComponent<Slider>().value;
@@ -211,7 +225,7 @@ namespace QuestAppLauncher
});
}
private void PersistConfig()
private async void PersistConfig()
{
bool saveConfig = false;
@@ -317,10 +331,10 @@ namespace QuestAppLauncher
}
// If we touched the config file or we deleted the hidden apps file, re-populate the grid
if (saveConfig || deletedHiddenAppsFile)
if (saveConfig || this.deletedHiddenAppsFile || this.deletedRenameFiles)
{
Debug.Log("Re-populating panel");
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
}
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ PlayerSettings:
16:10: 1
16:9: 1
Others: 1
bundleVersion: 0.9.1
bundleVersion: 0.10
preloadedAssets: []
metroInputSource: 0
wsaTransparentSwapchain: 0