Example #1
0
        /// <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"));
        }