All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.libimobiledevice.InstallationProxyClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2013 RoboVM AB
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.robovm.libimobiledevice;

import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;

import org.robovm.libimobiledevice.InstallationProxyClient.Options.ApplicationType;
import org.robovm.libimobiledevice.InstallationProxyClient.Options.PackageType;
import org.robovm.libimobiledevice.binding.InstProxyError;
import org.robovm.libimobiledevice.binding.InstproxyClientRef;
import org.robovm.libimobiledevice.binding.InstproxyClientRefOut;
import org.robovm.libimobiledevice.binding.LibIMobileDevice;
import org.robovm.libimobiledevice.binding.LibIMobileDeviceConstants;
import org.robovm.libimobiledevice.binding.LockdowndServiceDescriptorStruct;
import org.robovm.libimobiledevice.binding.PlistRef;
import org.robovm.libimobiledevice.binding.PlistRefOut;

import com.dd.plist.BinaryPropertyListWriter;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;

/**
 * Manages applications on a device.
 */
public class InstallationProxyClient implements AutoCloseable {
    
    public static final String SERVICE_NAME = LibIMobileDeviceConstants.INSTPROXY_SERVICE_NAME;
    
    protected InstproxyClientRef ref;

    InstallationProxyClient(InstproxyClientRef ref) {
        this.ref = ref;
    }
    
    /**
     * Creates a new {@link InstallationProxyClient} and makes a connection to 
     * the {@code com.apple.mobile.installation_proxy} service on the device.
     * 
     * @param device the device to connect to.
     * @param service the service descriptor returned by {@link LockdowndClient#startService(String)}.
     */
    public InstallationProxyClient(IDevice device, LockdowndServiceDescriptor service) {
        if (device == null) {
            throw new NullPointerException("device");
        }
        if (service == null) {
            throw new NullPointerException("service");
        }
        InstproxyClientRefOut refOut = new InstproxyClientRefOut();
        LockdowndServiceDescriptorStruct serviceStruct = new LockdowndServiceDescriptorStruct();
        serviceStruct.setPort((short) service.getPort());
        serviceStruct.setSslEnabled(service.isSslEnabled());
        try {
            checkResult(LibIMobileDevice.instproxy_client_new(device.getRef(), serviceStruct, refOut));
            this.ref = refOut.getValue();
        } finally {
            serviceStruct.delete();
            refOut.delete();
        }
    }

    /**
     * Lists all installed applications.
     * 
     * @return an {@link NSArray} of {@link NSDictionary}s holding information
     *         about the installed applications.
     */
    public NSArray browse() throws IOException {
        return browse(null);
    }
    
    /**
     * Lists installed applications of the specified type.
     *
     * @param type the type of applications to list. Either 
     *        {@link ApplicationType#User} or {@link ApplicationType#System}
     *        applications. Pass null to list all.
     * @return an {@link NSArray} of {@link NSDictionary}s holding information
     *         about the installed applications.
     */
    public NSArray browse(Options.ApplicationType type) throws IOException {
        PlistRefOut plistOut = new PlistRefOut();
        PlistRef clientOpts = new Options().applicationType(type).toPlistRef();
        try {
            checkResult(LibIMobileDevice.instproxy_browse(getRef(), clientOpts, plistOut));
            PlistRef plist = plistOut.getValue();
            return (NSArray) PlistUtil.toJavaPlist(plist);
        } finally {
            plistOut.delete();
            LibIMobileDevice.plist_free(clientOpts);
        }
    }
    
    /**
     * Installs an application on the device.
     *
     * @param pkgPath path of the installation package or app bundle dir inside 
     *        the AFC jail.
     * @param options the client options to use or null.
     *        Recognized options are {@link Options#iTunesMetadata(NSObject)},
     *        {@link Options#applicationSINF(NSObject)} and 
     *        {@link Options#packageType(PackageType)}.
     *        If {@code PackageType} is {@link PackageType#Developer} then 
     *        {@code pkgPath} points to an app bundle (.app directory) instead 
     *        of an install package.
     * @param callback callback which will be called for progress and status 
     *        information. If null is passed, this function will 
     *        run synchronously.
     */
    public void install(String pkgPath, Options options, StatusCallback callback) throws IOException {
        installOrUpgrade(pkgPath, options, callback, false);
    }
    
    /**
     * Upgrade an application on the device. This function is nearly the same as
     * {@link #install(String, Options, StatusCallback)}; the difference is that 
     * the installation progress on the device is faster if the application is 
     * already installed.     
     * 
     * @param pkgPath path of the installation package or app bundle dir inside 
     *        the AFC jail.
     * @param options the client options to use or null.
     *        Recognized options are {@link Options#iTunesMetadata(NSObject)},
     *        {@link Options#applicationSINF(NSObject)} and 
     *        {@link Options#packageType(PackageType)}.
     *        If {@code PackageType} is {@link PackageType#Developer} then 
     *        {@code pkgPath} points to an app bundle (.app directory) instead 
     *        of an install package.
     * @param callback callback which will be called for progress and status 
     *        information. If null is passed, this function will 
     *        run synchronously.
     */
    public void upgrade(String pkgPath, Options options, StatusCallback callback) throws IOException {
        installOrUpgrade(pkgPath, options, callback, true);
    }
    
    private void installOrUpgrade(String pkgPath, Options options, StatusCallback callback, boolean upgrade) throws IOException {
        PlistRef clientOpts = options.toPlistRef();
        try {
            long cb = 0L;
            int cbId = 0;
            if (callback != null) {
                cb = LibIMobileDevice.get_global_instproxy_status_cb();
                cbId = Callbacks.registerInstproxyCallback(callback);
            }
            if (upgrade) {
                checkResult(LibIMobileDevice.instproxy_upgrade(getRef(), pkgPath, 
                        clientOpts, cb, cbId));
            } else {
                checkResult(LibIMobileDevice.instproxy_install(getRef(), pkgPath, 
                        clientOpts, cb, cbId));
            }
        } finally {
            LibIMobileDevice.plist_free(clientOpts);
        }
    }
    
    protected InstproxyClientRef getRef() {
        checkDisposed();
        return ref;
    }
    
    protected final void checkDisposed() {
        if (ref == null) {
            throw new LibIMobileDeviceException("Already disposed");
        }
    }
    
    public synchronized void dispose() {
        checkDisposed();
        LibIMobileDevice.instproxy_client_free(ref);
        ref = null;
    }
    
    @Override
    public void close() {
        dispose();
    }
    
    private static void checkResult(InstProxyError result) {
        if (result != InstProxyError.INSTPROXY_E_SUCCESS) {
            throw new LibIMobileDeviceException(result.swigValue(), result.name());
        }
    }
    
    /**
     * Options used passed to the various {@link InstallationProxyClient}
     * methods.
     */
    public static class Options {
        
        public enum ApplicationType { User, System }
        public enum PackageType { CarrierBundle, Developer }
        public enum ArchiveType { ApplicationOnly, DocumentsOnly }
        
        private Boolean skipUninstall;
        private ApplicationType applicationType;
        private PackageType packageType;
        private ArchiveType archiveType;
        private NSObject applicationSINF;
        private NSObject iTunesMetadata;
        private NSObject returnAttributes;
        
        public Options skipUninstall(boolean skipUninstall) {
            this.skipUninstall = skipUninstall;
            return this;
        }
        public Options applicationType(ApplicationType applicationType) {
            this.applicationType = applicationType;
            return this;
        }
        public Options packageType(PackageType packageType) {
            this.packageType = packageType;
            return this;
        }
        public Options archiveType(ArchiveType archiveType) {
            this.archiveType = archiveType;
            return this;
        }
        public Options applicationSINF(NSObject applicationSINF) {
            this.applicationSINF = applicationSINF;
            return this;
        }
        public Options iTunesMetadata(NSObject iTunesMetadata) {
            this.iTunesMetadata = iTunesMetadata;
            return this;
        }
        public Options returnAttributes(NSObject returnAttributes) {
            this.returnAttributes = returnAttributes;
            return this;
        }
        
        PlistRef toPlistRef() throws IOException {
            NSDictionary dict = new NSDictionary();
            if (skipUninstall != null) {
                dict.put("SkipUninstall", skipUninstall.booleanValue());
            }
            if (applicationType != null) {
                dict.put("ApplicationType", applicationType.toString());
            }
            if (packageType != null) {
                dict.put("PackageType", packageType.toString());
            }
            if (archiveType != null) {
                dict.put("ArchiveType", archiveType.toString());
            }
            if (applicationSINF != null) {
                dict.put("ApplicationSINF", applicationSINF);
            }
            if (iTunesMetadata != null) {
                dict.put("iTunesMetadata", iTunesMetadata);
            }
            if (returnAttributes != null) {
                dict.put("ReturnAttributes", returnAttributes);
            }
            
            byte[] bytes = BinaryPropertyListWriter.writeToArray(dict);
            PlistRefOut plistOut = new PlistRefOut();
            LibIMobileDevice.plist_from_bin(bytes, bytes.length, plistOut);
            PlistRef plist = plistOut.getValue();
            plistOut.delete();
            
            return plist;
        }
        
    }
    
    /**
     * Callback which receives status information on an ongoing operation.
     */
    public interface StatusCallback {
        /**
         * Reports the progress of the operation.
         * 
         * @param status the current status.
         * @param percentComplete the progress in percent.
         */
        void progress(String status, int percentComplete);
        /**
         * Called once the operation has been completed successfully.
         */
        void success();
        /**
         * Called if the operation fails.
         * 
         * @param message the error message.
         */
        void error(String message);
    }
    
    private static void printUsageAndExit() {
        System.err.println(InstallationProxyClient.class.getName() + " [deviceid]  ...");
        System.err.println("  Actions:");
        System.err.println("    install    Performs an install using the package in the specified path.");
        System.err.println("    upgrade    Performs an upgrade using the package in the specified path.");
        System.exit(0);
    }
    
    public static void main(String[] args) throws Exception {
        String deviceId = null;
        String action = null;

        int index = 0;
        try {
            action = args[index++];
            if (action.matches("[0-9a-f]{40}")) {
                deviceId = action;
                action = args[index++];
            }
        
            if (!action.matches("install|upgrade")) {
                System.err.println("Unknown action: " + action);
                printUsageAndExit();
            }
            
            if (deviceId == null) {
                String[] udids = IDevice.listUdids();
                if (udids.length == 0) {
                    System.err.println("No device connected");
                    return;
                }
                if (udids.length > 1) {
                    System.err.println("More than 1 device connected ("
                            + Arrays.asList(udids) + "). Using " + udids[0]);
                }
                deviceId = udids[0];
            }
            
            try (IDevice device = new IDevice(deviceId)) {
                try (LockdowndClient lockdowndClient = new LockdowndClient(device, AfcClient.class.getSimpleName(), true)) {
                    LockdowndServiceDescriptor service = lockdowndClient.startService(SERVICE_NAME);
                    try (InstallationProxyClient client = new InstallationProxyClient(device, service)) {
                        String path = args[index];
                        Options options = new Options();
                        if (path.toLowerCase().endsWith(".app") || path.endsWith("/")) {
                            options.packageType(PackageType.Developer);
                        }
                        final CountDownLatch countDownLatch = new CountDownLatch(1);
                        StatusCallback callback = new StatusCallback() {
                            public void progress(String status, int percentComplete) {
                                System.out.format("[%3d%%] %s\n", percentComplete, status);
                            }
                            public void success() {
                                System.out.format("[100%%] Success!\n");
                                countDownLatch.countDown();
                            }
                            @Override
                            public void error(String message) {
                                System.out.format("Error: %s\n", message);
                                countDownLatch.countDown();
                            }
                        };
                        switch (action) {
                        case "install":
                            client.install(path, options, callback);
                            countDownLatch.await();
                            break;
                        case "upgrade":
                            client.upgrade(path, options, callback);
                            countDownLatch.await();
                            break;
                        }                    
                    }
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            printUsageAndExit();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy