blob: 4519770b9cc3c2be40643d17ea6496881471a2f3 [file] [log] [blame]
/*
* 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);
}
}