Home | History | Annotate | Download | only in project
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.project;
     18 
     19 import com.android.ddmlib.AndroidDebugBridge;
     20 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
     21 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
     22 import com.android.ddmlib.IDevice;
     23 import com.android.ddmlib.MultiLineReceiver;
     24 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     25 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
     26 
     27 import org.eclipse.core.resources.IProject;
     28 import org.eclipse.core.runtime.IPath;
     29 
     30 import java.util.HashSet;
     31 import java.util.Iterator;
     32 
     33 /**
     34  * Registers which apk was installed on which device.
     35  * <p/>
     36  * The goal of this class is to remember the installation of APKs on devices, and provide
     37  * information about whether a new APK should be installed on a device prior to running the
     38  * application from a launch configuration.
     39  * <p/>
     40  * The manager uses {@link IProject} and {@link IDevice} to identify the target device and the
     41  * (project generating the) APK. This ensures that disconnected and reconnected devices will
     42  * always receive new APKs (since the version may not match).
     43  * <p/>
     44  * This is a singleton. To get the instance, use {@link #getInstance()}
     45  */
     46 public final class ApkInstallManager {
     47 
     48     private final static ApkInstallManager sThis = new ApkInstallManager();
     49 
     50     /**
     51      * Internal struct to associate a project and a device.
     52      */
     53     private final static class ApkInstall {
     54         public ApkInstall(IProject project, String packageName, IDevice device) {
     55             this.project = project;
     56             this.packageName = packageName;
     57             this.device = device;
     58         }
     59 
     60         @Override
     61         public boolean equals(Object obj) {
     62             if (obj instanceof ApkInstall) {
     63                 ApkInstall apkObj = (ApkInstall)obj;
     64 
     65                 return (device == apkObj.device && project.equals(apkObj.project) &&
     66                         packageName.equals(apkObj.packageName));
     67             }
     68 
     69             return false;
     70         }
     71 
     72         @Override
     73         public int hashCode() {
     74             return (device.getSerialNumber() + project.getName() + packageName).hashCode();
     75         }
     76 
     77         final IProject project;
     78         final String packageName;
     79         final IDevice device;
     80     }
     81 
     82     /**
     83      * Receiver and parser for the "pm path package" command.
     84      */
     85     private final static class PmReceiver extends MultiLineReceiver {
     86         boolean foundPackage = false;
     87         @Override
     88         public void processNewLines(String[] lines) {
     89             // if the package if found, then pm will show a line starting with "package:/"
     90             if (foundPackage == false) { // just in case this is called several times for multilines
     91                 for (String line : lines) {
     92                     if (line.startsWith("package:/")) {
     93                         foundPackage = true;
     94                         break;
     95                     }
     96                 }
     97             }
     98         }
     99 
    100         @Override
    101         public boolean isCancelled() {
    102             return false;
    103         }
    104     }
    105 
    106     /**
    107      * Hashset of the list of installed package. Hashset used to ensure we don't re-add new
    108      * objects for the same app.
    109      */
    110     private final HashSet<ApkInstall> mInstallList = new HashSet<ApkInstall>();
    111 
    112     public static ApkInstallManager getInstance() {
    113         return sThis;
    114     }
    115 
    116     /**
    117      * Registers an installation of <var>project</var> onto <var>device</var>
    118      * @param project The project that was installed.
    119      * @param packageName the package name of the apk
    120      * @param device The device that received the installation.
    121      */
    122     public void registerInstallation(IProject project, String packageName, IDevice device) {
    123         synchronized (mInstallList) {
    124             mInstallList.add(new ApkInstall(project, packageName, device));
    125         }
    126     }
    127 
    128     /**
    129      * Returns whether a <var>project</var> was installed on the <var>device</var>.
    130      * @param project the project that may have been installed.
    131      * @param device the device that may have received the installation.
    132      * @return
    133      */
    134     public boolean isApplicationInstalled(IProject project, String packageName, IDevice device) {
    135         synchronized (mInstallList) {
    136             ApkInstall found = null;
    137             for (ApkInstall install : mInstallList) {
    138                 if (project.equals(install.project) && packageName.equals(install.packageName) &&
    139                         device == install.device) {
    140                     found = install;
    141                     break;
    142                 }
    143             }
    144 
    145             // check the app is still installed.
    146             if (found != null) {
    147                 try {
    148                     PmReceiver receiver = new PmReceiver();
    149                     found.device.executeShellCommand("pm path " + packageName, receiver);
    150                     if (receiver.foundPackage == false) {
    151                         mInstallList.remove(found);
    152                     }
    153 
    154                     return receiver.foundPackage;
    155                 } catch (Exception e) {
    156                     // failed to query pm? force reinstall.
    157                     return false;
    158                 }
    159             }
    160         }
    161         return false;
    162     }
    163 
    164     /**
    165      * Resets registered installations for a specific {@link IProject}.
    166      * <p/>This ensures that {@link #isApplicationInstalled(IProject, IDevice)} will always return
    167      * <code>null</code> for this specified project, for any device.
    168      * @param project the project for which to reset all installations.
    169      */
    170     public void resetInstallationFor(IProject project) {
    171         synchronized (mInstallList) {
    172             Iterator<ApkInstall> iterator = mInstallList.iterator();
    173             while (iterator.hasNext()) {
    174                 ApkInstall install = iterator.next();
    175                 if (install.project.equals(project)) {
    176                     iterator.remove();
    177                 }
    178             }
    179         }
    180     }
    181 
    182     private ApkInstallManager() {
    183         AndroidDebugBridge.addDeviceChangeListener(mDeviceChangeListener);
    184         AndroidDebugBridge.addDebugBridgeChangeListener(mDebugBridgeListener);
    185         GlobalProjectMonitor.getMonitor().addProjectListener(mProjectListener);
    186     }
    187 
    188     private IDebugBridgeChangeListener mDebugBridgeListener = new IDebugBridgeChangeListener() {
    189         /**
    190          * Responds to a bridge change by clearing the full installation list.
    191          *
    192          * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
    193          */
    194         @Override
    195         public void bridgeChanged(AndroidDebugBridge bridge) {
    196             // the bridge changed, there is no way to know which IDevice will be which.
    197             // We reset everything
    198             synchronized (mInstallList) {
    199                 mInstallList.clear();
    200             }
    201         }
    202     };
    203 
    204     private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() {
    205         /**
    206          * Responds to a device being disconnected by removing all installations related
    207          * to this device.
    208          *
    209          * @see IDeviceChangeListener#deviceDisconnected(IDevice)
    210          */
    211         @Override
    212         public void deviceDisconnected(IDevice device) {
    213             synchronized (mInstallList) {
    214                 Iterator<ApkInstall> iterator = mInstallList.iterator();
    215                 while (iterator.hasNext()) {
    216                     ApkInstall install = iterator.next();
    217                     if (install.device == device) {
    218                         iterator.remove();
    219                     }
    220                 }
    221             }
    222         }
    223 
    224         @Override
    225         public void deviceChanged(IDevice device, int changeMask) {
    226             // nothing to do.
    227         }
    228 
    229         @Override
    230         public void deviceConnected(IDevice device) {
    231             // nothing to do.
    232         }
    233     };
    234 
    235     private IProjectListener mProjectListener = new IProjectListener() {
    236         /**
    237          * Responds to a closed project by resetting all its installation.
    238          *
    239          * @see IProjectListener#projectClosed(IProject)
    240          */
    241         @Override
    242         public void projectClosed(IProject project) {
    243             resetInstallationFor(project);
    244         }
    245 
    246         /**
    247          * Responds to a deleted project by resetting all its installation.
    248          *
    249          * @see IProjectListener#projectDeleted(IProject)
    250          */
    251         @Override
    252         public void projectDeleted(IProject project) {
    253             resetInstallationFor(project);
    254         }
    255 
    256         @Override
    257         public void projectOpened(IProject project) {
    258             // nothing to do.
    259         }
    260 
    261         @Override
    262         public void projectOpenedWithWorkspace(IProject project) {
    263             // nothing to do.
    264         }
    265 
    266         @Override
    267         public void allProjectsOpenedWithWorkspace() {
    268             // nothing to do.
    269         }
    270 
    271         @Override
    272         public void projectRenamed(IProject project, IPath from) {
    273             // project renaming also triggers delete/open events so
    274             // there's nothing to do here (since delete will remove
    275             // whatever's linked to the project from the list).
    276         }
    277     };
    278 }
    279