/// <summary> /// Lists all processes running on the device. /// </summary> /// <param name="client"> /// A connection to ADB. /// </param> /// <param name="device"> /// The device on which to list the processes that are running. /// </param> /// <returns> /// An <see cref="IEnumerable{AndroidProcess}"/> that will iterate over all /// processes that are currently running on the device. /// </returns> public static IEnumerable <AndroidProcess> ListProcesses(this IAdbClient client, DeviceData device) { // There are a couple of gotcha's when listing processes on an Android device. // One way would be to run ps and parse the output. However, the output of // ps differents from Android version to Android version, is not delimited, nor // entirely fixed length, and some of the fields can be empty, so it's almost impossible // to parse correctly. // // The alternative is to directly read the values in /proc/[pid], pretty much like ps // does (see https://android.googlesource.com/platform/system/core/+/master/toolbox/ps.c). // // The easiest way to do the directory listings would be to use the SyncService; unfortunately, // the sync service doesn't work very well with /proc/ so we're back to using ls and taking it // from there. List <AndroidProcess> processes = new List <AndroidProcess>(); // List all processes by doing ls /proc/. // All subfolders which are completely numeric are PIDs // Android 7 and above ships with toybox (https://github.com/landley/toybox), which includes // an updated ls which behaves slightly different. // The -1 parameter is important to make sure each item gets its own line (it's an assumption we // make when parsing output); on Android 7 and above we may see things like: // 1 135 160 171 ioports timer_stats // 10 13533 16056 172 irq tty // 100 136 16066 173 kallsyms uid_cputime // but unfortunately older versions do not handle the -1 parameter well. So we need to branch based // on the API level. We do the branching on the device (inside a shell script) to avoid roundtrips. // This if/then/else syntax was tested on Android 2.x, 4.x and 7 ConsoleOutputReceiver receiver = new ConsoleOutputReceiver(); client.ExecuteShellCommand(device, @"SDK=""$(/system/bin/getprop ro.build.version.sdk)"" if [ $SDK -lt 24 ] then /system/bin/ls /proc/ else /system/bin/ls -1 /proc/ fi".Replace("\r\n", "\n"), receiver); Collection <int> pids = new Collection <int>(); var output = receiver.ToString(); using (StringReader reader = new StringReader(output)) { while (reader.Peek() > 0) { string line = reader.ReadLine(); if (!line.All(c => char.IsDigit(c))) { continue; } var pid = int.Parse(line); pids.Add(pid); } } // For each pid, we can get /proc/[pid]/stat, which contains the process information in a well-defined // format - see http://man7.org/linux/man-pages/man5/proc.5.html. // Doing cat on each file one by one takes too much time. Doing cat on all of them at the same time doesn't work // either, because the command line would be too long. // So we do it 25 processes at at time. StringBuilder catBuilder = new StringBuilder(); ProcessOutputReceiver processOutputReceiver = new ProcessOutputReceiver(); catBuilder.Append("cat "); for (int i = 0; i < pids.Count; i++) { catBuilder.Append($"/proc/{pids[i]}/cmdline /proc/{pids[i]}/stat "); if (i > 0 && (i % 25 == 0 || i == pids.Count - 1)) { client.ExecuteShellCommand(device, catBuilder.ToString(), processOutputReceiver); catBuilder.Clear(); catBuilder.Append("cat "); } } processOutputReceiver.Flush(); return(processOutputReceiver.Processes); }
public async Task InstallBuild(string buildPath, DeviceData device) { if (buildPath == null || !buildPath.EndsWith(".apk")) { throw new Exception("Only Installing APK builds are supported"); } // Get the package name CMDCommand command = new CMDCommand($"aapt dump badging \"{buildPath}\" | findstr -i \"package: name\""); command.Execute(null); InstallationPackageInfo installationPackage = InstallationPackageInfo.ParseAndroid(command.Output); ProgressChanged.Invoke(this, new ProgressChangedEventArgs(20, "Checking If build is already installed")); var packageManager = new PackageManager(_adbClient, device); bool reinstall = false; if (packageManager.Packages.ContainsKey(installationPackage.PackageName)) { if (_userPromptHandler != null) { reinstall = await _userPromptHandler.Invoke("Build Already Present on Android Device, Reinstall?"); if (!reinstall) { return; } } } ProgressChanged.Invoke(this, new ProgressChangedEventArgs(40, "Installing Apk")); packageManager.InstallPackage(buildPath, reinstall); ProgressChanged.Invoke(this, new ProgressChangedEventArgs(60, "Looking for expansin files")); // Look for obb files DirectoryInfo buildDirectory = new DirectoryInfo(Path.GetDirectoryName(buildPath)); FileInfo[] buildDirectoryFiles = buildDirectory.GetFiles(); IEnumerable <FileInfo> obbFiles = buildDirectoryFiles.Where(fileinfo => fileinfo.Extension == ".obb"); bool copyObbFiles = false; if (obbFiles.Count() > 0) { var expansionFilesFoundMessage = new StringBuilder(); expansionFilesFoundMessage.AppendLine("Apk expansion files found: "); foreach (FileInfo obbFile in obbFiles) { expansionFilesFoundMessage.AppendLine($"{obbFile}"); } expansionFilesFoundMessage.Append("Attempt to copy files to the device? Build might not work without expansion files."); copyObbFiles = await _userPromptHandler?.Invoke($"Apk Expansion Files Found: {expansionFilesFoundMessage}"); } if (copyObbFiles) { foreach (var obbFile in obbFiles) { using (var syncService = new SyncService(new AdbSocket(new IPEndPoint(IPAddress.Loopback, AdbClient.AdbServerPort)), device)) { using (var obbStream = File.OpenRead(obbFile.FullName)) { // OBB file name is build by {main/patch}.{versioncode}.{packagename}.obb string fileNamePrefix = obbFile.Name.Split('.').First(); string packageName = installationPackage.PackageName; string versionCode = installationPackage.VersionCode; string targetDirectory = $"sdcard/Android/obb/{packageName}"; string targetFileName = $"{fileNamePrefix}.{versionCode}.{packageName}.obb"; // Create direcotry on android device if it doesn't exist var adbShellOutputReciever = new ConsoleOutputReceiver(); _adbClient.ExecuteShellCommand(device, $"mkdir -p {targetDirectory}", adbShellOutputReciever); LoggingService.Logger.Info($"Device mkdir output: {adbShellOutputReciever}"); syncService.Push(obbStream, $"{targetDirectory}/{targetFileName}", 666, DateTime.Now, this, CancellationToken.None); } } } } ProgressChanged.Invoke(this, new ProgressChangedEventArgs(100, "Installing Apk")); }