From a9a09d41dcdfc360d2ebd97cdf1113d4aed0f647 Mon Sep 17 00:00:00 2001 From: Ruikai Liu Date: Thu, 27 Apr 2017 15:05:10 +0800 Subject: [PATCH 001/623] update userLibPath --- .../lib/src/main/java/com/lody/virtual/client/VClientImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java index 9f3eb9fca..1f2fedc76 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java @@ -363,7 +363,7 @@ private void startIOUniformer() { NativeEngine.redirectDirectory("/data/user_de/0/" + info.packageName, info.dataDir); } String libPath = new File(VEnvironment.getDataAppPackageDirectory(info.packageName), "lib").getAbsolutePath(); - String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), "lib").getAbsolutePath(); + String userLibPath = new File(VEnvironment.getDataUserPackageDirectory(userId, info.packageName), "lib").getAbsolutePath(); NativeEngine.redirectDirectory(userLibPath, libPath); NativeEngine.redirectDirectory("/data/data/" + info.packageName + "/lib/", libPath); NativeEngine.redirectDirectory("/data/user/0/" + info.packageName + "/lib/", libPath); From d65e59f30445e7a13d6fb77ac16c899c36f31282 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 15:49:12 +0800 Subject: [PATCH 002/623] Update: AMS. --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../server/am/VActivityManagerService.java | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/VirtualApp/gradle/wrapper/gradle-wrapper.properties b/VirtualApp/gradle/wrapper/gradle-wrapper.properties index 00a91bd00..0da39ca3f 100644 --- a/VirtualApp/gradle/wrapper/gradle-wrapper.properties +++ b/VirtualApp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java index 6e5ddbaa7..e13dda4b4 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java @@ -78,7 +78,7 @@ public class VActivityManagerService extends IActivityManager.Stub { private final SparseArray mPidsSelfLocked = new SparseArray(); private final ActivityStack mMainStack = new ActivityStack(this); private final Set mHistory = new HashSet(); - final ArrayMap> mServiceConnections + private final ArrayMap> mServiceConnections = new ArrayMap>(); private final ProcessMap mProcessNames = new ProcessMap(); private final PendingIntents mPendingIntents = new PendingIntents(); @@ -211,7 +211,7 @@ public ComponentName getActivityClassForToken(int userId, IBinder token) { } - public void processDead(ProcessRecord record) { + private void processDead(ProcessRecord record) { synchronized (mHistory) { Iterator iterator = mHistory.iterator(); while (iterator.hasNext()) { @@ -433,7 +433,7 @@ public boolean stopServiceToken(ComponentName className, IBinder token, int star /** * Extracting common method of stopService(see bringDownServiceIfNeededLocked in android source) - * @param r + * @param r ServiceRecord */ private void stopServiceCommon(ServiceRecord r) { if (r.hasAutoCreateConnections()) { @@ -442,10 +442,8 @@ private void stopServiceCommon(ServiceRecord r) { // Report to all of the connections that the service is no longer // available. - if (r.connections != null && r.connections.values() != null) { - Iterator> crs = r.connections.values().iterator(); - while (crs.hasNext()) { - ArrayList c = crs.next(); + if (r.connections != null && !r.connections.isEmpty()) { + for (ArrayList c : r.connections.values()) { for (int i = 0; i < c.size(); i++) { ConnectionRecord cr = c.get(i); // There is still a connection to the service that is @@ -454,7 +452,7 @@ private void stopServiceCommon(ServiceRecord r) { try { cr.conn.connected(r.name, null); } catch (Exception e) { - + e.printStackTrace(); } } } @@ -473,14 +471,16 @@ private void stopServiceCommon(ServiceRecord r) { IApplicationThreadCompat.scheduleUnbindService(r.process.appThread, r, ibr.intent); } catch (Exception e) { - + e.printStackTrace(); } } } } try { - IApplicationThreadCompat.scheduleStopService(r.process.appThread, r); + if (r.process != null) { + IApplicationThreadCompat.scheduleStopService(r.process.appThread, r); + } } catch (RemoteException e) { e.printStackTrace(); } @@ -493,7 +493,7 @@ public void setServiceForeground(ComponentName className, IBinder token, int id, } - final ProcessRecord getRecordForAppLocked(IBinder caller, int userId) { + private ProcessRecord getRecordForAppLocked(IBinder caller, int userId) { synchronized (mProcessNames) { ArrayMap> map = mProcessNames.getMap(); int N = map.size(); @@ -550,7 +550,7 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res IBinder binder = connection.asBinder(); ArrayList clist = r.connections.get(binder); if (clist == null) { - clist = new ArrayList(); + clist = new ArrayList<>(); r.connections.put(binder, clist); } clist.add(c); @@ -558,7 +558,7 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res clist = mServiceConnections.get(binder); if (clist == null) { - clist = new ArrayList(); + clist = new ArrayList<>(); mServiceConnections.put(binder, clist); } clist.add(c); @@ -591,7 +591,7 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res } } - void removeConnectionLocked( + private void removeConnectionLocked( ConnectionRecord c) { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; @@ -777,7 +777,7 @@ private int parseVPid(String stubProcessName) { try { return Integer.parseInt(stubProcessName.substring(prefix.length())); } catch (NumberFormatException e) { - e.printStackTrace(); + // ignore } } return -1; @@ -979,7 +979,7 @@ public List getProcessPkgList(int pid) { synchronized (mPidsSelfLocked) { ProcessRecord r = mPidsSelfLocked.get(pid); if (r != null) { - return new ArrayList(r.pkgList); + return new ArrayList<>(r.pkgList); } } return null; From a3762b2a17127d10aeb9a827546154763dc571d0 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 15:51:50 +0800 Subject: [PATCH 003/623] Config: Revert gradle version. --- VirtualApp/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VirtualApp/gradle/wrapper/gradle-wrapper.properties b/VirtualApp/gradle/wrapper/gradle-wrapper.properties index 0da39ca3f..00a91bd00 100644 --- a/VirtualApp/gradle/wrapper/gradle-wrapper.properties +++ b/VirtualApp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip From 9ec1906cdd36d44396674bf9c329b604762203c4 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 16:10:13 +0800 Subject: [PATCH 004/623] Code: remove GmsSupport. --- .../client/hook/secondary/GmsSupport.java | 77 ------------------- .../virtual/client/stub/StubManifest.java | 1 - .../virtual/server/pm/VAppManagerService.java | 5 -- 3 files changed, 83 deletions(-) delete mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java deleted file mode 100644 index 7fe0a1c74..000000000 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lody.virtual.client.hook.secondary; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; - -import com.lody.virtual.client.core.InstallStrategy; -import com.lody.virtual.client.core.VirtualCore; -import com.lody.virtual.server.pm.VAppManagerService; - -import java.util.Arrays; -import java.util.List; - -/** - * @author Lody - */ -public class GmsSupport { - - private static final List GOOGLE_APP = Arrays.asList( - "com.android.vending", - "com.google.android.play.games", - "com.google.android.wearable.app", - "com.google.android.wearable.app.cn" - ); - - private static final List GOOGLE_SERVICE = Arrays.asList( - "com.google.android.gsf", - "com.google.android.gms", - "com.google.android.gsf.login", - "com.google.android.backuptransport", - "com.google.android.backup", - "com.google.android.configupdater", - "com.google.android.syncadapters.contacts", - "com.google.android.feedback", - "com.google.android.onetimeinitializer", - "com.google.android.partnersetup", - "com.google.android.setupwizard", - "com.google.android.syncadapters.calendar" - ); - - - public static boolean isGmsFamilyPackage(String packageName) { - return packageName.equals("com.android.vending") - || packageName.equals("com.google.android.gms"); - } - - public static boolean isGoogleFrameworkInstalled() { - return VirtualCore.get().isAppInstalled("com.google.android.gms"); - } - - private static void installPackages(List list, int userId) { - VAppManagerService service = VAppManagerService.get(); - for (String packageName : list) { - if (service.isAppInstalledAsUser(userId, packageName)) { - continue; - } - ApplicationInfo info = null; - try { - info = VirtualCore.get().getUnHookPackageManager().getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - // Ignore - } - if (info == null || info.sourceDir == null) { - continue; - } - if (userId == 0) { - service.installPackage(info.sourceDir, InstallStrategy.DEPEND_SYSTEM_IF_EXIST, false); - } else { - service.installPackageAsUser(userId, packageName); - } - } - } - - public static void installGms(int userId) { - installPackages(GOOGLE_SERVICE, userId); - installPackages(GOOGLE_APP, userId); - } -} diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java index 55d208a45..c94b4127b 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java @@ -8,7 +8,6 @@ public class StubManifest { public static final String STUB_DEF_AUTHORITY = "virtual_stub_"; - public static final boolean ENABLE_GMS = false; public static String STUB_ACTIVITY = StubActivity.class.getName(); public static String STUB_DIALOG = StubDialog.class.getName(); public static String STUB_CP = StubContentProvider.class.getName(); diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java index a4c5590e8..39cdfdaca 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java @@ -9,8 +9,6 @@ import com.lody.virtual.client.core.InstallStrategy; import com.lody.virtual.client.core.VirtualCore; import com.lody.virtual.client.env.VirtualRuntime; -import com.lody.virtual.client.hook.secondary.GmsSupport; -import com.lody.virtual.client.stub.StubManifest; import com.lody.virtual.helper.collection.IntArray; import com.lody.virtual.helper.compat.NativeLibraryHelperCompat; import com.lody.virtual.helper.utils.ArrayUtils; @@ -75,9 +73,6 @@ public void scanApps() { synchronized (this) { mBooting = true; mPersistenceLayer.read(); - if (StubManifest.ENABLE_GMS && !GmsSupport.isGoogleFrameworkInstalled()) { - GmsSupport.installGms(0); - } mBooting = false; } } From 20310a406f876983eec9965c45cf37bb777bce68 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 16:20:17 +0800 Subject: [PATCH 005/623] Code: shrink GmsSupport. Code: fake-signature support. --- .../java/com/lody/virtual/GmsSupport.java | 27 +++++++ .../client/hook/secondary/GmsSupport.java | 77 ------------------- .../virtual/client/stub/StubManifest.java | 1 - .../virtual/helper/utils/ComponentUtils.java | 2 +- .../virtual/server/pm/VAppManagerService.java | 5 -- .../server/pm/parser/PackageParserEx.java | 9 ++- 6 files changed, 36 insertions(+), 85 deletions(-) create mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java delete mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java b/VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java new file mode 100644 index 000000000..637d33d77 --- /dev/null +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java @@ -0,0 +1,27 @@ +package com.lody.virtual; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import com.lody.virtual.client.core.InstallStrategy; +import com.lody.virtual.client.core.VirtualCore; +import com.lody.virtual.server.pm.VAppManagerService; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Lody + */ +public class GmsSupport { + + public static boolean isGmsFamilyPackage(String packageName) { + return packageName.equals("com.android.vending") + || packageName.equals("com.google.android.gms"); + } + + public static boolean isGoogleFrameworkInstalled() { + return VirtualCore.get().isAppInstalled("com.google.android.gms"); + } + +} \ No newline at end of file diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java deleted file mode 100644 index 7fe0a1c74..000000000 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lody.virtual.client.hook.secondary; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; - -import com.lody.virtual.client.core.InstallStrategy; -import com.lody.virtual.client.core.VirtualCore; -import com.lody.virtual.server.pm.VAppManagerService; - -import java.util.Arrays; -import java.util.List; - -/** - * @author Lody - */ -public class GmsSupport { - - private static final List GOOGLE_APP = Arrays.asList( - "com.android.vending", - "com.google.android.play.games", - "com.google.android.wearable.app", - "com.google.android.wearable.app.cn" - ); - - private static final List GOOGLE_SERVICE = Arrays.asList( - "com.google.android.gsf", - "com.google.android.gms", - "com.google.android.gsf.login", - "com.google.android.backuptransport", - "com.google.android.backup", - "com.google.android.configupdater", - "com.google.android.syncadapters.contacts", - "com.google.android.feedback", - "com.google.android.onetimeinitializer", - "com.google.android.partnersetup", - "com.google.android.setupwizard", - "com.google.android.syncadapters.calendar" - ); - - - public static boolean isGmsFamilyPackage(String packageName) { - return packageName.equals("com.android.vending") - || packageName.equals("com.google.android.gms"); - } - - public static boolean isGoogleFrameworkInstalled() { - return VirtualCore.get().isAppInstalled("com.google.android.gms"); - } - - private static void installPackages(List list, int userId) { - VAppManagerService service = VAppManagerService.get(); - for (String packageName : list) { - if (service.isAppInstalledAsUser(userId, packageName)) { - continue; - } - ApplicationInfo info = null; - try { - info = VirtualCore.get().getUnHookPackageManager().getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - // Ignore - } - if (info == null || info.sourceDir == null) { - continue; - } - if (userId == 0) { - service.installPackage(info.sourceDir, InstallStrategy.DEPEND_SYSTEM_IF_EXIST, false); - } else { - service.installPackageAsUser(userId, packageName); - } - } - } - - public static void installGms(int userId) { - installPackages(GOOGLE_SERVICE, userId); - installPackages(GOOGLE_APP, userId); - } -} diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java index 55d208a45..c94b4127b 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java @@ -8,7 +8,6 @@ public class StubManifest { public static final String STUB_DEF_AUTHORITY = "virtual_stub_"; - public static final boolean ENABLE_GMS = false; public static String STUB_ACTIVITY = StubActivity.class.getName(); public static String STUB_DIALOG = StubDialog.class.getName(); public static String STUB_CP = StubContentProvider.class.getName(); diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java index 093872d4f..c6007143f 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java @@ -8,7 +8,7 @@ import com.lody.virtual.client.core.VirtualCore; import com.lody.virtual.client.env.SpecialComponentList; -import com.lody.virtual.client.hook.secondary.GmsSupport; +import com.lody.virtual.GmsSupport; import com.lody.virtual.helper.compat.ObjectsCompat; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java index a4c5590e8..39cdfdaca 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java @@ -9,8 +9,6 @@ import com.lody.virtual.client.core.InstallStrategy; import com.lody.virtual.client.core.VirtualCore; import com.lody.virtual.client.env.VirtualRuntime; -import com.lody.virtual.client.hook.secondary.GmsSupport; -import com.lody.virtual.client.stub.StubManifest; import com.lody.virtual.helper.collection.IntArray; import com.lody.virtual.helper.compat.NativeLibraryHelperCompat; import com.lody.virtual.helper.utils.ArrayUtils; @@ -75,9 +73,6 @@ public void scanApps() { synchronized (this) { mBooting = true; mPersistenceLayer.read(); - if (StubManifest.ENABLE_GMS && !GmsSupport.isGoogleFrameworkInstalled()) { - GmsSupport.installGms(0); - } mBooting = false; } } diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java index fb93d4ae3..2bec701f1 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java @@ -50,7 +50,14 @@ public class PackageParserEx { public static VPackage parsePackage(File packageFile) throws Throwable { PackageParser parser = PackageParserCompat.createParser(packageFile); PackageParser.Package p = PackageParserCompat.parsePackage(parser, packageFile, 0); - PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM); + if (p.requestedPermissions.contains("android.permission.FAKE_PACKAGE_SIGNATURE") + && p.mAppMetaData != null + && p.mAppMetaData.containsKey("fake-signature")) { + String sig = p.mAppMetaData.getString("fake-signature"); + p.mSignatures = new Signature[]{new Signature(sig)}; + } else { + PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM); + } return buildPackageCache(p); } From 1cd5da53b89feba9898f5ef8a92de858fa29eac3 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 17:41:51 +0800 Subject: [PATCH 006/623] Fix: ActivityManager getRecentTasks bug. --- .../hook/proxies/am/ActivityManagerStub.java | 16 ++++++++++++---- .../server/pm/parser/PackageParserEx.java | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java index 899a813de..ccba8bdba 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java @@ -92,11 +92,19 @@ public Object call(Object who, Method method, Object... args) throws Throwable { continue; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - info.baseActivity = taskInfo.baseActivity; - info.topActivity = taskInfo.topActivity; + try { + info.topActivity = taskInfo.topActivity; + info.baseActivity = taskInfo.baseActivity; + } catch (Throwable e) { + // ignore + } + } + try { + info.origActivity = taskInfo.baseActivity; + info.baseIntent = taskInfo.baseIntent; + } catch (Throwable e) { + // ignore } - info.origActivity = taskInfo.baseActivity; - info.baseIntent = taskInfo.baseIntent; } return _infos; } diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java index 2bec701f1..a17603fc3 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java @@ -55,6 +55,7 @@ public static VPackage parsePackage(File packageFile) throws Throwable { && p.mAppMetaData.containsKey("fake-signature")) { String sig = p.mAppMetaData.getString("fake-signature"); p.mSignatures = new Signature[]{new Signature(sig)}; + VLog.d(TAG, "Using fake-signature feature on : " + p.packageName); } else { PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM); } From b886e766bd5e9d0fc19302ab10366f2160eeb273 Mon Sep 17 00:00:00 2001 From: Lody Date: Mon, 3 Jul 2017 17:57:25 +0800 Subject: [PATCH 007/623] Feature: skip dex opt. --- .../io/virtualapp/home/repo/AppRepository.java | 2 +- VirtualApp/lib/build.gradle | 1 + .../com/lody/virtual/client/VClientImpl.java | 13 ++++++++++--- .../virtual/client/core/InstallStrategy.java | 2 +- .../lody/virtual/client/core/VirtualCore.java | 2 +- .../lody/virtual/remote/InstalledAppInfo.java | 10 +++++----- .../lody/virtual/server/pm/PackageSetting.java | 8 ++++---- .../virtual/server/pm/VAppManagerService.java | 5 ++--- .../main/jniLibs/armeabi-v7a/libdalvikhack.so | Bin 0 -> 17980 bytes .../src/main/jniLibs/armeabi/libdalvikhack.so | Bin 0 -> 17972 bytes .../lib/src/main/jniLibs/x86/libdalvikhack.so | Bin 0 -> 9680 bytes .../lib/src/main/libs/dalvik_hack-3.0.0.5.jar | Bin 0 -> 30626 bytes 12 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 VirtualApp/lib/src/main/jniLibs/armeabi-v7a/libdalvikhack.so create mode 100644 VirtualApp/lib/src/main/jniLibs/armeabi/libdalvikhack.so create mode 100644 VirtualApp/lib/src/main/jniLibs/x86/libdalvikhack.so create mode 100644 VirtualApp/lib/src/main/libs/dalvik_hack-3.0.0.5.jar diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java index 383891a81..42fe3740a 100644 --- a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java +++ b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java @@ -147,7 +147,7 @@ private List convertPackageInfoToAppData(Context context, List`dWN1m`4eK{J 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 Jid`sY`ELo_6QuwE literal 0 HcmV?d00001 diff --git a/VirtualApp/lib/src/main/libs/dalvik_hack-3.0.0.5.jar b/VirtualApp/lib/src/main/libs/dalvik_hack-3.0.0.5.jar new file mode 100644 index 0000000000000000000000000000000000000000..0a42f12bf332a35761a2da9b940e8e52930a8872 GIT binary patch literal 30626 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�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 literal 0 HcmV?d00001 From 0c24f860aa887294c5355e1cd58c60b5752e15d4 Mon Sep 17 00:00:00 2001 From: Lody <2523313136@qq.com> Date: Mon, 3 Jul 2017 18:07:20 +0800 Subject: [PATCH 008/623] Add: Important message about Google Service. --- CHINESE.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/CHINESE.md b/CHINESE.md index f285cbcb5..fc7be75f2 100644 --- a/CHINESE.md +++ b/CHINESE.md @@ -18,14 +18,7 @@ VA目前被广泛应用于双开/多开,但它决不仅限于此,Android本 **您需要授权才可以使用lib的代码,VirtualApp已申请国家专利, 并获得软件著作权保护, 当你的行为对项目或是项目作者构成利益冲突时,我们将追究法律责任。若需使用本项目,请与作者联系。** -谁在使用本项目 -------------- -* 地铁跑酷 -* 骑士助手 -* X-Phone -* Dual app -* 机友精灵 -* 隐秘(PrivateMe) + 已支持的加固 ---------- @@ -38,6 +31,22 @@ VA目前被广泛应用于双开/多开,但它决不仅限于此,Android本 * (非VMP的加固都可以通过VA来脱壳,但目前本技术尚不公开) +在VA使用Google服务 +----------- +由于某些原因,VA无法直接运行官方的Google服务套件,但是我们提供了对`MicroGms`的支持, + +并屏蔽了外部的Google服务套件, + +因此您可以通过在VA中安装`MicroGms`来支持`Google服务`。 + +MicroGms套件可在此下载:[Download MicroGms](https://microg.org/download.html) + +必要模块: +* Services Core +* Services Framework Proxy +* Store + + 使用说明 ---------- From 945c50d9f76997f3514d0ab1fac5a364c4271643 Mon Sep 17 00:00:00 2001 From: Lody <2523313136@qq.com> Date: Mon, 3 Jul 2017 18:13:57 +0800 Subject: [PATCH 009/623] Update CHINESE.md --- CHINESE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHINESE.md b/CHINESE.md index fc7be75f2..7e4f40f60 100644 --- a/CHINESE.md +++ b/CHINESE.md @@ -16,7 +16,7 @@ VA目前被广泛应用于双开/多开,但它决不仅限于此,Android本 --- **您没有权利将VirtualApp的app模块作为您自己的app上架到软件市场,一经发现,后果你懂的。** -**您需要授权才可以使用lib的代码,VirtualApp已申请国家专利, 并获得软件著作权保护, 当你的行为对项目或是项目作者构成利益冲突时,我们将追究法律责任。若需使用本项目,请与作者联系。** +**当您需要将VA用于商业途径时,需要进行授权,因此请务必与作者联系(联系方式见下)。** From 9d20544a399dde3cd8d0477df4b6ffdd64f415df Mon Sep 17 00:00:00 2001 From: Lody Date: Tue, 4 Jul 2017 18:35:57 +0800 Subject: [PATCH 010/623] Feature: Choose Account Activity. --- VirtualApp/app/src/main/AndroidManifest.xml | 4 + VirtualApp/lib/src/main/AndroidManifest.xml | 16 + .../lody/virtual/client/core/VirtualCore.java | 4 +- .../client/hook/proxies/am/MethodProxies.java | 18 +- .../virtual/client/ipc/VAccountManager.java | 478 +++++++++------- .../com/lody/virtual/client/stub/AmsTask.java | 197 +++++++ .../stub/ChooseAccountTypeActivity.java | 175 ++++++ .../stub/ChooseTypeAndAccountActivity.java | 525 ++++++++++++++++++ .../main/res/layout/app_not_authorized.xml | 56 ++ .../main/res/layout/choose_account_row.xml | 26 + .../main/res/layout/choose_account_type.xml | 19 + .../res/layout/choose_type_and_account.xml | 62 +++ .../lib/src/main/res/values/strings.xml | 1 + 13 files changed, 1357 insertions(+), 224 deletions(-) create mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java create mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java create mode 100644 VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java create mode 100644 VirtualApp/lib/src/main/res/layout/app_not_authorized.xml create mode 100644 VirtualApp/lib/src/main/res/layout/choose_account_row.xml create mode 100644 VirtualApp/lib/src/main/res/layout/choose_account_type.xml create mode 100644 VirtualApp/lib/src/main/res/layout/choose_type_and_account.xml diff --git a/VirtualApp/app/src/main/AndroidManifest.xml b/VirtualApp/app/src/main/AndroidManifest.xml index 0f906711e..d9cc60d7b 100644 --- a/VirtualApp/app/src/main/AndroidManifest.xml +++ b/VirtualApp/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ @@ -20,16 +21,19 @@ diff --git a/VirtualApp/lib/src/main/AndroidManifest.xml b/VirtualApp/lib/src/main/AndroidManifest.xml index 6d89fced3..3dac0af8d 100644 --- a/VirtualApp/lib/src/main/AndroidManifest.xml +++ b/VirtualApp/lib/src/main/AndroidManifest.xml @@ -253,6 +253,22 @@ android:permission="android.permission.BIND_JOB_SERVICE" android:process=":x" /> + + + + 0) { - //noinspection deprecation - Drawable iconDrawable = resources.getDrawable(resId); - Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable); - if (newIcon != null) { - intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon); - } + int resId = resources.getIdentifier(icon.resourceName, "drawable", pkg); + if (resId > 0) { + //noinspection deprecation + Drawable iconDrawable = resources.getDrawable(resId); + Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable); + if (newIcon != null) { + intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon); } } } catch (Throwable e) { diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java index ea4724b9d..815bba6eb 100644 --- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java @@ -1,231 +1,285 @@ package com.lody.virtual.client.ipc; import android.accounts.Account; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorDescription; import android.accounts.IAccountManagerResponse; +import android.app.Activity; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import com.lody.virtual.client.env.VirtualRuntime; +import com.lody.virtual.client.stub.AmsTask; import com.lody.virtual.os.VUserHandle; import com.lody.virtual.server.IAccountManager; +import static com.lody.virtual.helper.compat.AccountManagerCompat.KEY_ANDROID_PACKAGE_NAME; + /** * @author Lody */ public class VAccountManager { - private static VAccountManager sMgr = new VAccountManager(); - - private IAccountManager mRemote; - - public static VAccountManager get() { - return sMgr; - } - - public IAccountManager getRemote() { - if (mRemote == null) { - Object remote = getStubInterface(); - mRemote = LocalProxyUtils.genProxy(IAccountManager.class, remote); - } - return mRemote; - } - - private Object getStubInterface() { - return IAccountManager.Stub - .asInterface(ServiceManagerNative.getService(ServiceManagerNative.ACCOUNT)); - } - - public AuthenticatorDescription[] getAuthenticatorTypes() { - try { - return getRemote().getAuthenticatorTypes(VUserHandle.myUserId()); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public void removeAccount(IAccountManagerResponse response, Account account, boolean expectActivityLaunch) { - try { - getRemote().removeAccount(VUserHandle.myUserId(), response, account, expectActivityLaunch); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void getAuthToken(IAccountManagerResponse response, Account account, String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch, Bundle loginOptions) { - try { - getRemote().getAuthToken(VUserHandle.myUserId(), response, account, authTokenType, notifyOnAuthFailure, expectActivityLaunch, loginOptions); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public boolean addAccountExplicitly(Account account, String password, Bundle extras) { - try { - return getRemote().addAccountExplicitly(VUserHandle.myUserId(), account, password, extras); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public Account[] getAccounts(String type) { - try { - return getRemote().getAccounts(VUserHandle.myUserId(), type); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public String peekAuthToken(Account account, String authTokenType) { - try { - return getRemote().peekAuthToken(VUserHandle.myUserId(), account, authTokenType); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public String getPreviousName(Account account) { - try { - return getRemote().getPreviousName(VUserHandle.myUserId(), account); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { - try { - getRemote().hasFeatures(VUserHandle.myUserId(), response, account, features); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public boolean accountAuthenticated(Account account) { - try { - return getRemote().accountAuthenticated(VUserHandle.myUserId(), account); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public void clearPassword(Account account) { - try { - getRemote().clearPassword(VUserHandle.myUserId(), account); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void renameAccount(IAccountManagerResponse response, Account accountToRename, String newName) { - try { - getRemote().renameAccount(VUserHandle.myUserId(), response, accountToRename, newName); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void setPassword(Account account, String password) { - try { - getRemote().setPassword(VUserHandle.myUserId(), account, password); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void addAccount(IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) { - try { - getRemote().addAccount(VUserHandle.myUserId(), response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void updateCredentials(IAccountManagerResponse response, Account account, String authTokenType, boolean expectActivityLaunch, Bundle loginOptions) { - try { - getRemote().updateCredentials(VUserHandle.myUserId(), response, account, authTokenType, expectActivityLaunch, loginOptions); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public boolean removeAccountExplicitly(Account account) { - try { - return getRemote().removeAccountExplicitly(VUserHandle.myUserId(), account); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public void setUserData(Account account, String key, String value) { - try { - getRemote().setUserData(VUserHandle.myUserId(), account, key, value); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void editProperties(IAccountManagerResponse response, String accountType, boolean expectActivityLaunch) { - try { - getRemote().editProperties(VUserHandle.myUserId(), response, accountType, expectActivityLaunch); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void getAuthTokenLabel(IAccountManagerResponse response, String accountType, String authTokenType) { - try { - getRemote().getAuthTokenLabel(VUserHandle.myUserId(), response, accountType, authTokenType); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void confirmCredentials(IAccountManagerResponse response, Account account, Bundle options, boolean expectActivityLaunch) { - try { - getRemote().confirmCredentials(VUserHandle.myUserId(), response, account, options, expectActivityLaunch); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void invalidateAuthToken(String accountType, String authToken) { - try { - getRemote().invalidateAuthToken(VUserHandle.myUserId(), accountType, authToken); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) { - try { - getRemote().getAccountsByFeatures(VUserHandle.myUserId(), response, type, features); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public void setAuthToken(Account account, String authTokenType, String authToken) { - try { - getRemote().setAuthToken(VUserHandle.myUserId(), account, authTokenType, authToken); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Object getPassword(Account account) { - try { - return getRemote().getPassword(VUserHandle.myUserId(), account); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } - - public String getUserData(Account account, String key) { - try { - return getRemote().getUserData(VUserHandle.myUserId(), account, key); - } catch (RemoteException e) { - return VirtualRuntime.crash(e); - } - } + private static VAccountManager sMgr = new VAccountManager(); + + private IAccountManager mRemote; + + public static VAccountManager get() { + return sMgr; + } + + public IAccountManager getRemote() { + if (mRemote == null) { + Object remote = getStubInterface(); + mRemote = LocalProxyUtils.genProxy(IAccountManager.class, remote); + } + return mRemote; + } + + private Object getStubInterface() { + return IAccountManager.Stub + .asInterface(ServiceManagerNative.getService(ServiceManagerNative.ACCOUNT)); + } + + public AuthenticatorDescription[] getAuthenticatorTypes() { + try { + return getRemote().getAuthenticatorTypes(VUserHandle.myUserId()); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public void removeAccount(IAccountManagerResponse response, Account account, boolean expectActivityLaunch) { + try { + getRemote().removeAccount(VUserHandle.myUserId(), response, account, expectActivityLaunch); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void getAuthToken(IAccountManagerResponse response, Account account, String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch, Bundle loginOptions) { + try { + getRemote().getAuthToken(VUserHandle.myUserId(), response, account, authTokenType, notifyOnAuthFailure, expectActivityLaunch, loginOptions); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public boolean addAccountExplicitly(Account account, String password, Bundle extras) { + try { + return getRemote().addAccountExplicitly(VUserHandle.myUserId(), account, password, extras); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public Account[] getAccounts(int userId, String type) { + try { + return getRemote().getAccounts(userId, type); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public Account[] getAccounts(String type) { + try { + return getRemote().getAccounts(VUserHandle.myUserId(), type); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public String peekAuthToken(Account account, String authTokenType) { + try { + return getRemote().peekAuthToken(VUserHandle.myUserId(), account, authTokenType); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public String getPreviousName(Account account) { + try { + return getRemote().getPreviousName(VUserHandle.myUserId(), account); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { + try { + getRemote().hasFeatures(VUserHandle.myUserId(), response, account, features); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public boolean accountAuthenticated(Account account) { + try { + return getRemote().accountAuthenticated(VUserHandle.myUserId(), account); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public void clearPassword(Account account) { + try { + getRemote().clearPassword(VUserHandle.myUserId(), account); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void renameAccount(IAccountManagerResponse response, Account accountToRename, String newName) { + try { + getRemote().renameAccount(VUserHandle.myUserId(), response, accountToRename, newName); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void setPassword(Account account, String password) { + try { + getRemote().setPassword(VUserHandle.myUserId(), account, password); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void addAccount(int userId, IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) { + try { + getRemote().addAccount(userId, response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void addAccount(IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) { + try { + getRemote().addAccount(VUserHandle.myUserId(), response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void updateCredentials(IAccountManagerResponse response, Account account, String authTokenType, boolean expectActivityLaunch, Bundle loginOptions) { + try { + getRemote().updateCredentials(VUserHandle.myUserId(), response, account, authTokenType, expectActivityLaunch, loginOptions); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public boolean removeAccountExplicitly(Account account) { + try { + return getRemote().removeAccountExplicitly(VUserHandle.myUserId(), account); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public void setUserData(Account account, String key, String value) { + try { + getRemote().setUserData(VUserHandle.myUserId(), account, key, value); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void editProperties(IAccountManagerResponse response, String accountType, boolean expectActivityLaunch) { + try { + getRemote().editProperties(VUserHandle.myUserId(), response, accountType, expectActivityLaunch); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void getAuthTokenLabel(IAccountManagerResponse response, String accountType, String authTokenType) { + try { + getRemote().getAuthTokenLabel(VUserHandle.myUserId(), response, accountType, authTokenType); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void confirmCredentials(IAccountManagerResponse response, Account account, Bundle options, boolean expectActivityLaunch) { + try { + getRemote().confirmCredentials(VUserHandle.myUserId(), response, account, options, expectActivityLaunch); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void invalidateAuthToken(String accountType, String authToken) { + try { + getRemote().invalidateAuthToken(VUserHandle.myUserId(), accountType, authToken); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) { + try { + getRemote().getAccountsByFeatures(VUserHandle.myUserId(), response, type, features); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void setAuthToken(Account account, String authTokenType, String authToken) { + try { + getRemote().setAuthToken(VUserHandle.myUserId(), account, authTokenType, authToken); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public Object getPassword(Account account) { + try { + return getRemote().getPassword(VUserHandle.myUserId(), account); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + public String getUserData(Account account, String key) { + try { + return getRemote().getUserData(VUserHandle.myUserId(), account, key); + } catch (RemoteException e) { + return VirtualRuntime.crash(e); + } + } + + /** + * Asks the user to add an account of a specified type. The authenticator + * for this account type processes this request with the appropriate user + * interface. If the user does elect to create a new account, the account + * name is returned. + *

+ *

This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + *

+ * + */ + public AccountManagerFuture addAccount(final int userId, final String accountType, + final String authTokenType, final String[] requiredFeatures, + final Bundle addAccountOptions, + final Activity activity, AccountManagerCallback callback, Handler handler) { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + final Bundle optionsIn = new Bundle(); + if (addAccountOptions != null) { + optionsIn.putAll(addAccountOptions); + } + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, "android"); + + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + addAccount(userId, mResponse, accountType, authTokenType, + requiredFeatures, activity != null, optionsIn); + } + }.start(); + } } diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java new file mode 100644 index 000000000..52179bd80 --- /dev/null +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java @@ -0,0 +1,197 @@ +package com.lody.virtual.client.stub; + +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.IAccountManagerResponse; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; + +import com.lody.virtual.client.env.VirtualRuntime; +import com.lody.virtual.helper.utils.VLog; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static android.accounts.AccountManager.ERROR_CODE_BAD_ARGUMENTS; +import static android.accounts.AccountManager.ERROR_CODE_CANCELED; +import static android.accounts.AccountManager.ERROR_CODE_INVALID_RESPONSE; +import static android.accounts.AccountManager.ERROR_CODE_NETWORK_ERROR; +import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION; +import static android.accounts.AccountManager.KEY_INTENT; +import static com.lody.virtual.helper.compat.AccountManagerCompat.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE; +import static com.lody.virtual.helper.compat.AccountManagerCompat.ERROR_CODE_USER_RESTRICTED; + +public abstract class AmsTask extends FutureTask implements AccountManagerFuture { + protected final IAccountManagerResponse mResponse; + final Handler mHandler; + final AccountManagerCallback mCallback; + final Activity mActivity; + + public AmsTask(Activity activity, Handler handler, AccountManagerCallback callback) { + super(new Callable() { + @Override + public Bundle call() throws Exception { + throw new IllegalStateException("this should never be called"); + } + }); + + mHandler = handler; + mCallback = callback; + mActivity = activity; + mResponse = new Response(); + } + + public final AccountManagerFuture start() { + try { + doWork(); + } catch (RemoteException e) { + setException(e); + } + return this; + } + + @Override + protected void set(Bundle bundle) { + // TODO: somehow a null is being set as the result of the Future. Log this + // case to help debug where this is occurring. When this bug is fixed this + // condition statement should be removed. + if (bundle == null) { + VLog.e("AccountManager", "the bundle must not be null", new Exception()); + + } + super.set(bundle); + } + + public abstract void doWork() throws RemoteException; + + private Bundle internalGetResult(Long timeout, TimeUnit unit) + throws OperationCanceledException, IOException, AuthenticatorException { + try { + if (timeout == null) { + return get(); + } else { + return get(timeout, unit); + } + } catch (CancellationException e) { + throw new OperationCanceledException(); + } catch (TimeoutException e) { + // fall through and cancel + } catch (InterruptedException e) { + // fall through and cancel + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else if (cause instanceof UnsupportedOperationException) { + throw new AuthenticatorException(cause); + } else if (cause instanceof AuthenticatorException) { + throw (AuthenticatorException) cause; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new IllegalStateException(cause); + } + } finally { + cancel(true /* interrupt if running */); + } + throw new OperationCanceledException(); + } + + @Override + public Bundle getResult() + throws OperationCanceledException, IOException, AuthenticatorException { + return internalGetResult(null, null); + } + + @Override + public Bundle getResult(long timeout, TimeUnit unit) + throws OperationCanceledException, IOException, AuthenticatorException { + return internalGetResult(timeout, unit); + } + + @Override + protected void done() { + if (mCallback != null) { + postToHandler(mHandler, mCallback, this); + } + } + + /** + * Handles the responses from the AccountManager + */ + private class Response extends IAccountManagerResponse.Stub { + @Override + public void onResult(Bundle bundle) { + Intent intent = bundle.getParcelable(KEY_INTENT); + if (intent != null && mActivity != null) { + // since the user provided an Activity we will silently start intents + // that we see + mActivity.startActivity(intent); + // leave the Future running to wait for the real response to this request + } else if (bundle.getBoolean("retry")) { + try { + doWork(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } else { + set(bundle); + } + } + + @Override + public void onError(int code, String message) { + if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED + || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) { + // the authenticator indicated that this request was canceled or we were + // forbidden to fulfill; cancel now + cancel(true /* mayInterruptIfRunning */); + return; + } + setException(convertErrorToException(code, message)); + } + } + + private Exception convertErrorToException(int code, String message) { + if (code == ERROR_CODE_NETWORK_ERROR) { + return new IOException(message); + } + + if (code == ERROR_CODE_UNSUPPORTED_OPERATION) { + return new UnsupportedOperationException(message); + } + + if (code == ERROR_CODE_INVALID_RESPONSE) { + return new AuthenticatorException(message); + } + + if (code == ERROR_CODE_BAD_ARGUMENTS) { + return new IllegalArgumentException(message); + } + + return new AuthenticatorException(message); + } + + private void postToHandler(Handler handler, final AccountManagerCallback callback, + final AccountManagerFuture future) { + handler = handler == null ? VirtualRuntime.getUIHandler() : handler; + handler.post(new Runnable() { + @Override + public void run() { + callback.run(future); + } + }); + } +} \ No newline at end of file diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java new file mode 100644 index 000000000..3bd4dcdd3 --- /dev/null +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java @@ -0,0 +1,175 @@ +package com.lody.virtual.client.stub; + +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.lody.virtual.R; +import com.lody.virtual.client.core.VirtualCore; +import com.lody.virtual.client.ipc.VAccountManager; +import com.lody.virtual.helper.utils.VLog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @hide + */ +public class ChooseAccountTypeActivity extends Activity { + private static final String TAG = "AccountChooser"; + + private HashMap mTypeToAuthenticatorInfo = new HashMap(); + private ArrayList mAuthenticatorInfosToDisplay; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes + Set setOfAllowableAccountTypes = null; + String[] validAccountTypes = getIntent().getStringArrayExtra( + ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); + if (validAccountTypes != null) { + setOfAllowableAccountTypes = new HashSet<>(validAccountTypes.length); + Collections.addAll(setOfAllowableAccountTypes, validAccountTypes); + } + + // create a map of account authenticators + buildTypeToAuthDescriptionMap(); + + // Create a list of authenticators that are allowable. Filter out those that + // don't match the allowable account types, if provided. + mAuthenticatorInfosToDisplay = new ArrayList<>(mTypeToAuthenticatorInfo.size()); + for (Map.Entry entry: mTypeToAuthenticatorInfo.entrySet()) { + final String type = entry.getKey(); + final AuthInfo info = entry.getValue(); + if (setOfAllowableAccountTypes != null + && !setOfAllowableAccountTypes.contains(type)) { + continue; + } + mAuthenticatorInfosToDisplay.add(info); + } + + if (mAuthenticatorInfosToDisplay.isEmpty()) { + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "no allowable account types"); + setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); + finish(); + return; + } + + if (mAuthenticatorInfosToDisplay.size() == 1) { + setResultAndFinish(mAuthenticatorInfosToDisplay.get(0).desc.type); + return; + } + + setContentView(R.layout.choose_account_type); + // Setup the list + ListView list = (ListView) findViewById(android.R.id.list); + // Use an existing ListAdapter that will map an array of strings to TextViews + list.setAdapter(new AccountArrayAdapter(this, + android.R.layout.simple_list_item_1, mAuthenticatorInfosToDisplay)); + list.setChoiceMode(ListView.CHOICE_MODE_NONE); + list.setTextFilterEnabled(false); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, int position, long id) { + setResultAndFinish(mAuthenticatorInfosToDisplay.get(position).desc.type); + } + }); + } + + private void setResultAndFinish(final String type) { + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type); + setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); + VLog.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: " + + "selected account type " + type); + finish(); + } + + private void buildTypeToAuthDescriptionMap() { + for(AuthenticatorDescription desc : VAccountManager.get().getAuthenticatorTypes()) { + String name = null; + Drawable icon = null; + try { + Resources res = VirtualCore.get().getResources(desc.packageName); + icon = res.getDrawable(desc.iconId); + final CharSequence sequence = res.getText(desc.labelId); + name = sequence.toString(); + name = sequence.toString(); + } catch (Resources.NotFoundException e) { + // Nothing we can do much here, just log + VLog.w(TAG, "No icon resource for account type " + desc.type); + } + AuthInfo authInfo = new AuthInfo(desc, name, icon); + mTypeToAuthenticatorInfo.put(desc.type, authInfo); + } + } + + private static class AuthInfo { + final AuthenticatorDescription desc; + final String name; + final Drawable drawable; + + AuthInfo(AuthenticatorDescription desc, String name, Drawable drawable) { + this.desc = desc; + this.name = name; + this.drawable = drawable; + } + } + + private static class ViewHolder { + ImageView icon; + TextView text; + } + + private static class AccountArrayAdapter extends ArrayAdapter { + private LayoutInflater mLayoutInflater; + private ArrayList mInfos; + + AccountArrayAdapter(Context context, int textViewResourceId, + ArrayList infos) { + super(context, textViewResourceId, infos); + mInfos = infos; + mLayoutInflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null); + holder = new ViewHolder(); + holder.text = (TextView) convertView.findViewById(R.id.account_row_text); + holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + holder.text.setText(mInfos.get(position).name); + holder.icon.setImageDrawable(mInfos.get(position).drawable); + + return convertView; + } + } +} diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java new file mode 100644 index 000000000..53d09cb1b --- /dev/null +++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java @@ -0,0 +1,525 @@ +package com.lody.virtual.client.stub; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorDescription; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +import com.lody.virtual.R; +import com.lody.virtual.client.ipc.VAccountManager; +import com.lody.virtual.helper.utils.VLog; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class ChooseTypeAndAccountActivity extends Activity + implements AccountManagerCallback { + private static final String TAG = "AccountChooser"; + + /** + * A Parcelable ArrayList of Account objects that limits the choosable accounts to those + * in this list, if this parameter is supplied. + */ + public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts"; + + /** + * A Parcelable ArrayList of String objects that limits the accounts to choose to those + * that match the types in this list, if this parameter is supplied. This list is also + * used to filter the allowable account types if add account is selected. + */ + public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes"; + + /** + * This is passed as the addAccountOptions parameter in AccountManager.addAccount() + * if it is called. + */ + public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions"; + + /** + * This is passed as the requiredFeatures parameter in AccountManager.addAccount() + * if it is called. + */ + public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY = + "addAccountRequiredFeatures"; + + /** + * This is passed as the authTokenType string in AccountManager.addAccount() + * if it is called. + */ + public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType"; + + /** + * If set then the specified account is already "selected". + */ + public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount"; + + /** + * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity} + * will have no effect. + */ + @Deprecated + public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT = + "alwaysPromptForAccount"; + + /** + * If set then this string willb e used as the description rather than + * the default. + */ + public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = + "descriptionTextOverride"; + + public static final int REQUEST_NULL = 0; + public static final int REQUEST_CHOOSE_TYPE = 1; + public static final int REQUEST_ADD_ACCOUNT = 2; + + private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest"; + private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; + private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName"; + private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount"; + private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList"; + public static final String KEY_USER_ID = "userId"; + + private static final int SELECTED_ITEM_NONE = -1; + + private Set mSetOfAllowableAccounts; + private Set mSetOfRelevantAccountTypes; + private String mSelectedAccountName = null; + private boolean mSelectedAddNewAccount = false; + private String mDescriptionOverride; + + private ArrayList mAccounts; + private int mPendingRequest = REQUEST_NULL; + private Parcelable[] mExistingAccounts = null; + private int mSelectedItemIndex; + private Button mOkButton; + private int mCallingUserId; + private boolean mDontShowPicker; + + @Override + public void onCreate(Bundle savedInstanceState) { + // save some items we use frequently + final Intent intent = getIntent(); + + if (savedInstanceState != null) { + mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); + mExistingAccounts = + savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); + + // Makes sure that any user selection is preserved across orientation changes. + mSelectedAccountName = savedInstanceState.getString( + KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME); + + mSelectedAddNewAccount = savedInstanceState.getBoolean( + KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); + mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST); + mCallingUserId = savedInstanceState.getInt(KEY_USER_ID); + } else { + mPendingRequest = REQUEST_NULL; + mExistingAccounts = null; + mCallingUserId = intent.getIntExtra(KEY_USER_ID, -1); + // If the selected account as specified in the intent matches one in the list we will + // show is as pre-selected. + Account selectedAccount = intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); + if (selectedAccount != null) { + mSelectedAccountName = selectedAccount.name; + } + } + VLog.v(TAG, "selected account name is " + mSelectedAccountName); + + mSetOfAllowableAccounts = getAllowableAccountSet(intent); + mSetOfRelevantAccountTypes = getReleventAccountTypes(intent); + mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); + + mAccounts = getAcceptableAccountChoices(VAccountManager.get()); + + if (mDontShowPicker) { + super.onCreate(savedInstanceState); + return; + } + + // In cases where the activity does not need to show an account picker, cut the chase + // and return the result directly. Eg: + // Single account -> select it directly + // No account -> launch add account activity directly + if (mPendingRequest == REQUEST_NULL) { + // If there are no relevant accounts and only one relevant account type go directly to + // add account. Otherwise let the user choose. + if (mAccounts.isEmpty()) { + setNonLabelThemeAndCallSuperCreate(savedInstanceState); + if (mSetOfRelevantAccountTypes.size() == 1) { + runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next()); + } else { + startChooseAccountTypeActivity(); + } + } + } + + String[] listItems = getListOfDisplayableOptions(mAccounts); + mSelectedItemIndex = getItemIndexToSelect( + mAccounts, mSelectedAccountName, mSelectedAddNewAccount); + + super.onCreate(savedInstanceState); + setContentView(R.layout.choose_type_and_account); + overrideDescriptionIfSupplied(mDescriptionOverride); + populateUIAccountList(listItems); + + // Only enable "OK" button if something has been selected. + mOkButton = (Button) findViewById(android.R.id.button2); + mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE); + } + + @Override + protected void onDestroy() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()"); + } + super.onDestroy(); + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest); + if (mPendingRequest == REQUEST_ADD_ACCOUNT) { + outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts); + } + if (mSelectedItemIndex != SELECTED_ITEM_NONE) { + if (mSelectedItemIndex == mAccounts.size()) { + outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true); + } else { + outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); + outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME, + mAccounts.get(mSelectedItemIndex).name); + } + } + outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts); + } + + public void onCancelButtonClicked(View view) { + onBackPressed(); + } + + public void onOkButtonClicked(View view) { + if (mSelectedItemIndex == mAccounts.size()) { + // Selected "Add New Account" option + startChooseAccountTypeActivity(); + } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) { + onAccountSelected(mAccounts.get(mSelectedItemIndex)); + } + } + + // Called when the choose account type activity (for adding an account) returns. + // If it was a success read the account and set it in the result. In all cases + // return the result and finish this activity. + @Override + protected void onActivityResult(final int requestCode, final int resultCode, + final Intent data) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + if (data != null && data.getExtras() != null) data.getExtras().keySet(); + Bundle extras = data != null ? data.getExtras() : null; + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode + + ", resCode=" + resultCode + ", extras=" + extras + ")"); + } + + // we got our result, so clear the fact that we had a pending request + mPendingRequest = REQUEST_NULL; + + if (resultCode == RESULT_CANCELED) { + // if canceling out of addAccount and the original state caused us to skip this, + // finish this activity + if (mAccounts.isEmpty()) { + setResult(Activity.RESULT_CANCELED); + finish(); + } + return; + } + + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CHOOSE_TYPE) { + if (data != null) { + String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + if (accountType != null) { + runAddAccountForAuthenticator(accountType); + return; + } + } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account " + + "type, pretending the request was canceled"); + } else if (requestCode == REQUEST_ADD_ACCOUNT) { + String accountName = null; + String accountType = null; + + if (data != null) { + accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); + accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + } + + if (accountName == null || accountType == null) { + Account[] currentAccounts = VAccountManager.get().getAccounts(mCallingUserId, null); + Set preExistingAccounts = new HashSet<>(); + for (Parcelable accountParcel : mExistingAccounts) { + preExistingAccounts.add((Account) accountParcel); + } + for (Account account : currentAccounts) { + if (!preExistingAccounts.contains(account)) { + accountName = account.name; + accountType = account.type; + break; + } + } + } + + if (accountName != null || accountType != null) { + setResultAndFinish(accountName, accountType); + return; + } + } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added " + + "account, pretending the request was canceled"); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); + } + setResult(Activity.RESULT_CANCELED); + finish(); + } + + protected void runAddAccountForAuthenticator(String type) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "runAddAccountForAuthenticator: " + type); + } + final Bundle options = getIntent().getBundleExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE); + final String[] requiredFeatures = getIntent().getStringArrayExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY); + final String authTokenType = getIntent().getStringExtra( + ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING); + VAccountManager.get().addAccount(mCallingUserId, type, authTokenType, requiredFeatures, + options, null /* activity */, this /* callback */, null /* Handler */); + } + + @Override + public void run(final AccountManagerFuture accountManagerFuture) { + try { + final Bundle accountManagerResult = accountManagerFuture.getResult(); + final Intent intent = accountManagerResult.getParcelable( + AccountManager.KEY_INTENT); + if (intent != null) { + mPendingRequest = REQUEST_ADD_ACCOUNT; + mExistingAccounts = VAccountManager.get().getAccounts(mCallingUserId, null); + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, REQUEST_ADD_ACCOUNT); + return; + } + } catch (OperationCanceledException e) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } catch (IOException e) { + } catch (AuthenticatorException e) { + } + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server"); + setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); + finish(); + } + + /** + * The default activity theme shows label at the top. Set a theme which does + * not show label, which effectively makes the activity invisible. Note that + * no content is being set. If something gets set, using this theme may be + * useless. + */ + private void setNonLabelThemeAndCallSuperCreate(Bundle savedInstanceState) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setTheme(android.R.style.Theme_Material_Light_Dialog_NoActionBar); + } else { + setTheme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar); + } + super.onCreate(savedInstanceState); + } + + private void onAccountSelected(Account account) { + Log.d(TAG, "selected account " + account); + setResultAndFinish(account.name, account.type); + } + + private void setResultAndFinish(final String accountName, final String accountType) { + Bundle bundle = new Bundle(); + bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); + bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); + setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); + VLog.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " + + "selected account " + accountName + ", " + accountType); + finish(); + } + + private void startChooseAccountTypeActivity() { + VLog.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()"); + final Intent intent = new Intent(this, ChooseAccountTypeActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, + getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY)); + intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE, + getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE)); + intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY, + getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY)); + intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, + getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING)); + startActivityForResult(intent, REQUEST_CHOOSE_TYPE); + mPendingRequest = REQUEST_CHOOSE_TYPE; + } + + /** + * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE. + * An index value of accounts.size() indicates 'Add account' option. + */ + private int getItemIndexToSelect(ArrayList accounts, String selectedAccountName, + boolean selectedAddNewAccount) { + // If "Add account" option was previously selected by user, preserve it across + // orientation changes. + if (selectedAddNewAccount) { + return accounts.size(); + } + // search for the selected account name if present + for (int i = 0; i < accounts.size(); i++) { + if (accounts.get(i).name.equals(selectedAccountName)) { + return i; + } + } + // no account selected. + return SELECTED_ITEM_NONE; + } + + private String[] getListOfDisplayableOptions(ArrayList accounts) { + // List of options includes all accounts found together with "Add new account" as the + // last item in the list. + String[] listItems = new String[accounts.size() + 1]; + for (int i = 0; i < accounts.size(); i++) { + listItems[i] = accounts.get(i).name; + } + listItems[accounts.size()] = getResources().getString( + R.string.add_account_button_label); + return listItems; + } + + /** + * Create a list of Account objects for each account that is acceptable. Filter out + * accounts that don't match the allowable types, if provided, or that don't match the + * allowable accounts, if provided. + */ + private ArrayList getAcceptableAccountChoices(VAccountManager accountManager) { + final Account[] accounts = accountManager.getAccounts(mCallingUserId, null); + ArrayList accountsToPopulate = new ArrayList<>(accounts.length); + for (Account account : accounts) { + if (mSetOfAllowableAccounts != null && !mSetOfAllowableAccounts.contains(account)) { + continue; + } + if (mSetOfRelevantAccountTypes != null + && !mSetOfRelevantAccountTypes.contains(account.type)) { + continue; + } + accountsToPopulate.add(account); + } + return accountsToPopulate; + } + + /** + * Return a set of account types specified by the intent as well as supported by the + * AccountManager. + */ + private Set getReleventAccountTypes(final Intent intent) { + // An account type is relevant iff it is allowed by the caller and supported by the account + // manager. + Set setOfRelevantAccountTypes; + final String[] allowedAccountTypes = + intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); + AuthenticatorDescription[] descs = VAccountManager.get().getAuthenticatorTypes(); + Set supportedAccountTypes = new HashSet(descs.length); + for (AuthenticatorDescription desc : descs) { + supportedAccountTypes.add(desc.type); + } + if (allowedAccountTypes != null) { + setOfRelevantAccountTypes = new HashSet<>(); + Collections.addAll(setOfRelevantAccountTypes, allowedAccountTypes); + setOfRelevantAccountTypes.retainAll(supportedAccountTypes); + } else { + setOfRelevantAccountTypes = supportedAccountTypes; + } + return setOfRelevantAccountTypes; + } + + /** + * Returns a set of whitelisted accounts given by the intent or null if none specified by the + * intent. + */ + private Set getAllowableAccountSet(final Intent intent) { + Set setOfAllowableAccounts = null; + final ArrayList validAccounts = + intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST); + if (validAccounts != null) { + setOfAllowableAccounts = new HashSet<>(validAccounts.size()); + for (Parcelable parcelable : validAccounts) { + setOfAllowableAccounts.add((Account) parcelable); + } + } + return setOfAllowableAccounts; + } + + /** + * Overrides the description text view for the picker activity if specified by the intent. + * If not specified then makes the description invisible. + */ + private void overrideDescriptionIfSupplied(String descriptionOverride) { + TextView descriptionView = (TextView) findViewById(R.id.description); + if (!TextUtils.isEmpty(descriptionOverride)) { + descriptionView.setText(descriptionOverride); + } else { + descriptionView.setVisibility(View.GONE); + } + } + + /** + * Populates the UI ListView with the given list of items and selects an item + * based on {@code mSelectedItemIndex} member variable. + */ + private void populateUIAccountList(String[] listItems) { + ListView list = (ListView) findViewById(android.R.id.list); + list.setAdapter(new ArrayAdapter<>(this, + android.R.layout.simple_list_item_single_choice, listItems)); + list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + list.setItemsCanFocus(false); + list.setOnItemClickListener( + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + mSelectedItemIndex = position; + mOkButton.setEnabled(true); + } + }); + if (mSelectedItemIndex != SELECTED_ITEM_NONE) { + list.setItemChecked(mSelectedItemIndex, true); + VLog.v(TAG, "List item " + mSelectedItemIndex + " should be selected"); + } + } +} diff --git a/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml b/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml new file mode 100644 index 000000000..c7e936172 --- /dev/null +++ b/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + +