/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.motorolamobility.studio.android.db.devices.model; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.jdt.core.JavaConventions; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.osgi.util.NLS; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; import com.motorola.studio.android.adt.DDMSFacade; import com.motorola.studio.android.adt.DdmsRunnable; import com.motorola.studio.android.adt.StudioAndroidEventManager; import com.motorola.studio.android.adt.StudioAndroidEventManager.EventType; import com.motorola.studio.android.common.log.StudioLogger; import com.motorolamobility.studio.android.db.core.CanRefreshStatus; import com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode; import com.motorolamobility.studio.android.db.core.ui.ISaveStateTreeNode; import com.motorolamobility.studio.android.db.core.ui.ITreeNode; import com.motorolamobility.studio.android.db.core.ui.view.SaveStateManager; import com.motorolamobility.studio.android.db.devices.DbDevicesPlugin; import com.motorolamobility.studio.android.db.devices.i18n.DbDevicesNLS; import com.motorolamobility.studio.android.db.devices.utils.DeviceDbUtils; /** * This class represents a tree node for a given Android Device. */ public class DeviceNode extends AbstractTreeNode implements IDeviceNode, ISaveStateTreeNode { private static final String MEMENTO_FILTER_TYPE = "filterAppsWithDb"; //$NON-NLS-1$ private static final String MEMENTO_FILTER_KEY = "filterEnabled"; //$NON-NLS-1$ private static final String ICON_PATH = "icons/obj16/device.png"; //$NON-NLS-1$ private String serialNumber; private boolean filterAppsWithDb; private String deviceName; private final PackageChangedListener listener = new PackageChangedListener(this); @SuppressWarnings("unused") private DeviceNode() { //Forcing user to use a proper constructor } /** * Creates a new Devicenode based on it's serial number. * @param serialNumber the device's serial number. * @param parent this node parent. */ public DeviceNode(String serialNumber, ITreeNode parent) { super(serialNumber, DDMSFacade.getNameBySerialNumber(serialNumber), parent); this.serialNumber = serialNumber; this.deviceName = getName(); ImageDescriptor icon = DbDevicesPlugin.imageDescriptorFromPlugin(DbDevicesPlugin.PLUGIN_ID, ICON_PATH); setIcon(icon); StudioAndroidEventManager.addEventListener(EventType.PACKAGE_INSTALLED, listener); StudioAndroidEventManager.addEventListener(EventType.PACKAGE_UNINSTALLED, listener); } /* (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#canRefresh() */ @Override public IStatus canRefresh() { List<IStatus> childrenStatus = new ArrayList<IStatus>(getChildren().size()); String message = NLS.bind(DbDevicesNLS.DeviceNode_Cant_Refresh_Node, getName()); for (ITreeNode treeNode : getChildren()) { IStatus nodecanRefresh = treeNode.canRefresh(); if (!nodecanRefresh.isOK()) { childrenStatus.add(nodecanRefresh); message = nodecanRefresh.getMessage(); break; } } IStatus status = null; if (!childrenStatus.isEmpty()) { status = new CanRefreshStatus(CanRefreshStatus.ASK_USER, DbDevicesPlugin.PLUGIN_ID, childrenStatus.toArray(new IStatus[0]), message); } return status != null ? status : Status.OK_STATUS; } /* (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#refresh() */ @SuppressWarnings("unchecked") @Override public void refresh() { clear(); IStatus status = null; Collection<String> loadedApps = null; Map<String, String> listInstalledPackages = null; Integer totalFiltered = 0; try { Object[] installedPackagesContainer = DeviceDbUtils.listInstalledPackages(serialNumber, mustFilterAppsWithDb()); listInstalledPackages = (Map<String, String>) installedPackagesContainer[0]; totalFiltered = (Integer) installedPackagesContainer[1]; } catch (IOException e) { status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID, NLS.bind( DbDevicesNLS.DeviceNode_CouldNotLoadInstalledApps, deviceName)); } if (listInstalledPackages != null) { loadedApps = listInstalledPackages.keySet(); } List<ITreeNode> childNodes = new ArrayList<ITreeNode>(loadedApps != null ? loadedApps.size() + 1 : 1); try { if (DDMSFacade.hasSDCard(serialNumber)) { ExtStorageNode extStorageNode = new ExtStorageNode(this); childNodes.add(extStorageNode); } } catch (IOException e1) { status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID, NLS.bind( DbDevicesNLS.DeviceNode_CouldNotVerifySdCard, deviceName)); } if (loadedApps != null) { for (String app : loadedApps) { if (JavaConventions.validatePackageName(app, JavaCore.VERSION_1_5, JavaCore.VERSION_1_5).isOK()) { ApplicationNode appNode = new ApplicationNode(app, DeviceNode.this); childNodes.add(appNode); } } if (!childNodes.isEmpty()) { putChildren(childNodes); } } updateName(totalFiltered); setNodeStatus(status != null ? status : Status.OK_STATUS); } /** * @param totalFiltered */ private void updateName(Integer totalFiltered) { if (mustFilterAppsWithDb() && (totalFiltered > 0) && !getChildren().isEmpty()) { setName(deviceName + NLS.bind(DbDevicesNLS.DeviceNode_X_Apps_Filtered, totalFiltered)); } else { DeviceNode.this.setName(deviceName); } } /* (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#isLeaf() */ @Override public boolean isLeaf() { return false; } /** * @return the filterAppWithDb */ public boolean mustFilterAppsWithDb() { return this.filterAppsWithDb; } /** * @param filterAppWithDb the filterAppWithDb to set */ public void setFilterAppWithDb(boolean filterAppWithDb) { this.filterAppsWithDb = filterAppWithDb; saveState(SaveStateManager.getInstance().getPrefNode()); } /** * @return the serialNumber */ public String getSerialNumber() { return serialNumber; } /* * (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.ISaveStateTreeNode#saveState(org.eclipse.core.runtime.preferences.IEclipsePreferences) */ public void saveState(IEclipsePreferences preferences) { String id = deviceName != null ? deviceName : getName(); Preferences filterNode = preferences.node(MEMENTO_FILTER_TYPE); Preferences deviceNode = filterNode.node(id); deviceNode.putBoolean(MEMENTO_FILTER_KEY, mustFilterAppsWithDb()); } /* * (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.ISaveStateTreeNode#restoreState(org.eclipse.core.runtime.preferences.IEclipsePreferences) */ public void restoreState(IEclipsePreferences preferences) { boolean filterDbApps = true; String deviceName = this.deviceName != null ? this.deviceName : getName(); try { if (preferences.nodeExists(MEMENTO_FILTER_TYPE)) { Preferences filterNode = preferences.node(MEMENTO_FILTER_TYPE); if (filterNode.nodeExists(deviceName)) { Preferences deviceNode = filterNode.node(deviceName); filterDbApps = deviceNode.getBoolean(MEMENTO_FILTER_KEY, true); } } } catch (BackingStoreException e) { StudioLogger.error("Could not contact backing store: ", e.getMessage()); //$NON-NLS-1$ } setFilterAppWithDb(filterDbApps); } /** * Listener called when a package (application) is installed / uninstalled */ private static class PackageChangedListener implements DdmsRunnable { private final DeviceNode deviceNode; public PackageChangedListener(DeviceNode deviceNode) { this.deviceNode = deviceNode; } public void run(final String serialNumber) { Thread thread = new Thread(new Runnable() { @SuppressWarnings("unchecked") public void run() { IStatus status = null; //it is an expensive operation Map<String, String> listInstalledPackages = null; try { Object[] installedPackagesContainer = DeviceDbUtils.listInstalledPackages(serialNumber, deviceNode.mustFilterAppsWithDb()); listInstalledPackages = (Map<String, String>) installedPackagesContainer[0]; Integer totalFiltered = (Integer) installedPackagesContainer[1]; List<ITreeNode> childNodes = deviceNode.getChildren(); List<ITreeNode> toRemoveNodes = new ArrayList<ITreeNode>(); Set<String> newApplications = new HashSet<String>(); newApplications.addAll(listInstalledPackages.keySet()); if (childNodes != null) { Iterator<ITreeNode> iterator = childNodes.iterator(); while (iterator.hasNext()) { ITreeNode node = iterator.next(); if (node instanceof ApplicationNode) { ApplicationNode appNode = (ApplicationNode) node; if (!listInstalledPackages.containsKey(appNode.getId())) { //app was removed => mark to remove node from the tree toRemoveNodes.add(node); } else { //app remains installed => remove it from newApplication set newApplications.remove(appNode.getId()); } } } //now the items in newApplication set must be the new applications => add nodes to each one of them for (String newApp : newApplications) { ApplicationNode appNode = new ApplicationNode(newApp, deviceNode); deviceNode.putChild(appNode); } for (ITreeNode node : toRemoveNodes) { //remove the nodes marked deviceNode.removeChild(node); } deviceNode.updateName(totalFiltered); } } catch (IOException e) { status = new Status(IStatus.ERROR, DbDevicesPlugin.PLUGIN_ID, NLS.bind( DbDevicesNLS.DeviceNode_CouldNotLoadInstalledApps, deviceNode.getName())); } deviceNode.setNodeStatus(status != null ? status : Status.OK_STATUS); } }); thread.start(); } } /* (non-Javadoc) * @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#clean() */ @Override public void cleanUp() { super.cleanUp(); StudioAndroidEventManager.removeEventListener(EventType.PACKAGE_INSTALLED, listener); StudioAndroidEventManager.removeEventListener(EventType.PACKAGE_UNINSTALLED, listener); } }