| package org.jetbrains.android.database; |
| |
| import com.android.ddmlib.AndroidDebugBridge; |
| import com.android.ddmlib.FileListingService; |
| import com.android.ddmlib.IDevice; |
| import com.android.ddmlib.MultiLineReceiver; |
| import com.android.tools.idea.ddms.DeviceRenderer; |
| import com.intellij.database.dataSource.AbstractDataSourceConfigurable; |
| import com.intellij.database.dataSource.DatabaseDriver; |
| import com.intellij.database.util.DbImplUtil; |
| import com.intellij.facet.ProjectFacetManager; |
| import com.intellij.javaee.dataSource.AbstractDataSourceConfigurable; |
| import com.intellij.javaee.dataSource.DatabaseDriver; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.ComboBox; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.ui.FieldPanel; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.components.JBRadioButton; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.ui.update.Activatable; |
| import com.intellij.util.ui.update.UiNotifyConnector; |
| import org.jetbrains.android.dom.manifest.Manifest; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.android.sdk.AndroidSdkUtils; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.*; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| public class AndroidDataSourcePropertiesDialog extends AbstractDataSourceConfigurable<AndroidDbManager, AndroidDataSource> implements |
| Disposable { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.database.AndroidDataSourcePropertiesDialog"); |
| private static final String[] DEFAULT_EXTERNAL_DB_PATTERNS = new String[]{"files/"}; |
| |
| private DefaultComboBoxModel myDeviceComboBoxModel = new DefaultComboBoxModel(); |
| private String myMissingDeviceIds; |
| |
| private ComboBox myDeviceComboBox; |
| private ComboBox myPackageNameComboBox; |
| private ComboBox myDataBaseComboBox; |
| private JPanel myPanel; |
| private FieldPanel myNameField; |
| private JPanel myConfigurationPanel; |
| private JBRadioButton myExternalStorageRadioButton; |
| private JBRadioButton myInternalStorageRadioButton; |
| |
| private IDevice mySelectedDevice = null; |
| private final Map<String, List<String>> myDatabaseMap = ContainerUtil.newLinkedHashMap(); |
| private final AndroidDebugBridge.IDeviceChangeListener myDeviceListener; |
| |
| private final AndroidDataSource myTempDataSource; |
| |
| protected AndroidDataSourcePropertiesDialog(@NotNull AndroidDbManager manager, @NotNull Project project, @NotNull AndroidDataSource dataSource) { |
| super(manager, dataSource, project); |
| |
| myTempDataSource = dataSource.copy(); |
| |
| myConfigurationPanel.setBorder(IdeBorderFactory.createEmptyBorder(10, 0, 0, 0)); |
| myNameField.setLabelText("Name:"); |
| myNameField.createComponent(); |
| myNameField.setChangeListener(new Runnable() { |
| @Override |
| public void run() { |
| fireStateChanged(); |
| } |
| }); |
| |
| myDeviceComboBox.setRenderer(new DeviceRenderer.DeviceComboBoxRenderer() { |
| @Override |
| protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { |
| if (value instanceof String) { |
| append(AndroidDbUtil.getPresentableNameFromDeviceId((String)value)); |
| } |
| else { |
| super.customizeCellRenderer(list, value, index, selected, hasFocus); |
| } |
| } |
| }); |
| myDeviceComboBox.setPreferredSize(new Dimension(300, myDeviceComboBox.getPreferredSize().height)); |
| |
| myDeviceListener = new AndroidDebugBridge.IDeviceChangeListener() { |
| @Override |
| public void deviceConnected(IDevice device) { |
| addDeviceToComboBoxIfNeeded(device); |
| } |
| |
| @Override |
| public void deviceDisconnected(IDevice device) { |
| } |
| |
| @Override |
| public void deviceChanged(IDevice device, int changeMask) { |
| if ((changeMask & IDevice.CHANGE_STATE) == changeMask) { |
| addDeviceToComboBoxIfNeeded(device); |
| } |
| } |
| }; |
| |
| myDeviceComboBox.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| updateDataBases(); |
| } |
| }); |
| |
| ActionListener l = new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| updateDbCombo(); |
| } |
| }; |
| myPackageNameComboBox.addActionListener(l); |
| myExternalStorageRadioButton.addActionListener(l); |
| myInternalStorageRadioButton.addActionListener(l); |
| |
| new UiNotifyConnector.Once(myPanel, new Activatable.Adapter() { |
| @Override |
| public void showNotify() { |
| loadDevices(); |
| updateDataBases(); |
| updateDbCombo(); |
| registerDeviceListener(); |
| } |
| }); |
| new UiNotifyConnector(myPanel, new Activatable.Adapter() { |
| @Override |
| public void showNotify() { |
| checkDriverPresence(); |
| } |
| }); |
| } |
| |
| @NotNull |
| @Override |
| public AndroidDataSource getTempDataSource() { |
| saveData(myTempDataSource); |
| return myTempDataSource; |
| } |
| |
| private void addDeviceToComboBoxIfNeeded(@NotNull final IDevice device) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (!device.isOnline()) { |
| return; |
| } |
| final String deviceId = AndroidDbUtil.getDeviceId(device); |
| |
| if (deviceId == null || deviceId.length() == 0) { |
| return; |
| } |
| for (int i = 0; i < myDeviceComboBoxModel.getSize(); i++) { |
| final Object element = myDeviceComboBoxModel.getElementAt(i); |
| |
| if (device.equals(element)) { |
| return; |
| } |
| } |
| myDeviceComboBoxModel.addElement(device); |
| |
| if (myMissingDeviceIds != null && myMissingDeviceIds.equals(deviceId)) { |
| myDeviceComboBoxModel.removeElement(myMissingDeviceIds); |
| myMissingDeviceIds = null; |
| } |
| } |
| }, ModalityState.stateForComponent(myPanel)); |
| } |
| |
| private void loadDevices() { |
| final AndroidDebugBridge bridge = AndroidSdkUtils.getDebugBridge(myProject); |
| final IDevice[] devices = bridge != null ? getDevicesWithValidDeviceId(bridge) : new IDevice[0]; |
| final String deviceId = myDataSource.getState().deviceId; |
| final DefaultComboBoxModel model = new DefaultComboBoxModel(devices); |
| Object selectedItem = null; |
| |
| if (deviceId != null && deviceId.length() > 0) { |
| for (IDevice device : devices) { |
| if (deviceId.equals(AndroidDbUtil.getDeviceId(device))) { |
| selectedItem = device; |
| break; |
| } |
| } |
| |
| if (selectedItem == null) { |
| model.addElement(deviceId); |
| myMissingDeviceIds = deviceId; |
| selectedItem = deviceId; |
| } |
| } |
| myDeviceComboBoxModel = model; |
| myDeviceComboBox.setModel(model); |
| |
| if (selectedItem != null) { |
| myDeviceComboBox.setSelectedItem(selectedItem); |
| } |
| } |
| |
| @NotNull |
| private static IDevice[] getDevicesWithValidDeviceId(@NotNull AndroidDebugBridge bridge) { |
| final List<IDevice> result = new ArrayList<IDevice>(); |
| |
| for (IDevice device : bridge.getDevices()) { |
| if (device.isOnline()) { |
| final String deviceId = AndroidDbUtil.getDeviceId(device); |
| |
| if (deviceId != null && deviceId.length() > 0) { |
| result.add(device); |
| } |
| } |
| } |
| return result.toArray(new IDevice[result.size()]); |
| } |
| |
| private void updateDataBases() { |
| if (!myPanel.isShowing()) return; |
| final Object selectedItem = myDeviceComboBox.getSelectedItem(); |
| IDevice selectedDevice = selectedItem instanceof IDevice ? (IDevice)selectedItem : null; |
| |
| if (selectedDevice == null) { |
| myDatabaseMap.clear(); |
| myPackageNameComboBox.setModel(new DefaultComboBoxModel()); |
| myDataBaseComboBox.setModel(new DefaultComboBoxModel()); |
| } |
| else if (!selectedDevice.equals(mySelectedDevice)) { |
| loadDatabases(selectedDevice); |
| myPackageNameComboBox.setModel(new DefaultComboBoxModel(ArrayUtil.toStringArray(myDatabaseMap.keySet()))); |
| updateDbCombo(); |
| } |
| mySelectedDevice = selectedDevice; |
| } |
| |
| private void updateDbCombo() { |
| if (!myPanel.isShowing()) return; // comboboxes do weird stuff when loosing focus |
| String selectedPackage = getSelectedPackage(); |
| |
| if (myInternalStorageRadioButton.isSelected()) { |
| List<String> dbList = myDatabaseMap.get(selectedPackage); |
| myDataBaseComboBox.setModel(new DefaultComboBoxModel(ArrayUtil.toStringArray(dbList))); |
| } |
| else { |
| myDataBaseComboBox.setModel(new DefaultComboBoxModel(DEFAULT_EXTERNAL_DB_PATTERNS)); |
| } |
| } |
| |
| @NotNull |
| private String getSelectedPackage() { |
| return (String)myPackageNameComboBox.getEditor().getItem(); |
| } |
| |
| @NotNull |
| private String getSelectedDatabase() { |
| return (String)myDataBaseComboBox.getEditor().getItem(); |
| } |
| |
| private void loadDatabases(@NotNull IDevice device) { |
| myDatabaseMap.clear(); |
| |
| final FileListingService service = device.getFileListingService(); |
| if (service == null) return; |
| |
| final Set<String> packages = new HashSet<String>(); |
| |
| for (AndroidFacet facet : ProjectFacetManager.getInstance(myProject).getFacets(AndroidFacet.ID)) { |
| final Manifest manifest = facet.getManifest(); |
| |
| if (manifest != null) { |
| final String aPackage = manifest.getPackage().getStringValue(); |
| |
| if (aPackage != null && aPackage.length() > 0) { |
| packages.add(aPackage); |
| } |
| } |
| } |
| if (packages.isEmpty()) return; |
| |
| final long startTime = System.currentTimeMillis(); |
| boolean tooLong = false; |
| |
| for (String aPackage : packages) { |
| myDatabaseMap.put(aPackage, tooLong ? Collections.<String>emptyList(): loadDatabases(device, aPackage)); |
| |
| if (System.currentTimeMillis() - startTime > 4000) { |
| tooLong = true; |
| } |
| } |
| } |
| |
| @NotNull |
| private static List<String> loadDatabases(@NotNull IDevice device, @NotNull final String packageName) { |
| final List<String> result = new ArrayList<String>(); |
| |
| try { |
| device.executeShellCommand("run-as " + packageName + " ls " + AndroidDbUtil.getInternalDatabasesRemoteDirPath(packageName), new MultiLineReceiver() { |
| @Override |
| public void processNewLines(String[] lines) { |
| for (String line : lines) { |
| if (line.length() > 0 && !line.contains(" ")) { |
| result.add(line); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isCancelled() { |
| return false; |
| } |
| }, 2, TimeUnit.SECONDS); |
| } |
| catch (Exception e) { |
| LOG.debug(e); |
| } |
| return result; |
| } |
| |
| private String getSelectedDeviceId() { |
| Object item = myDeviceComboBox.getSelectedItem(); |
| if (item == null) return null; // "no devices" case should not throw AE |
| |
| if (item instanceof String) return (String)item; |
| |
| assert item instanceof IDevice; |
| final String deviceId = AndroidDbUtil.getDeviceId((IDevice)item); |
| return deviceId != null ? deviceId : ""; |
| } |
| |
| @Nullable |
| @Override |
| public JComponent createComponent() { |
| return myPanel; |
| } |
| |
| public void saveData(@NotNull AndroidDataSource dataSource) { |
| dataSource.setName(getNameValue()); |
| AndroidDataSource.State state = dataSource.getState(); |
| state.deviceId = getSelectedDeviceId(); |
| state.packageName = getSelectedPackage(); |
| state.databaseName = getSelectedDatabase(); |
| state.external = myExternalStorageRadioButton.isSelected(); |
| dataSource.resetUrl(); |
| } |
| |
| @Override |
| public void apply() { |
| saveData(myDataSource); |
| |
| if (DbImplUtil.canConnectTo(myDataSource)) { |
| AndroidSynchronizeHandler.doSynchronize(myProject, Collections.singletonList(myDataSource)); |
| } |
| |
| if (isNewDataSource()) { |
| myManager.processAddOrRemove(myDataSource, true); |
| } |
| } |
| |
| @Override |
| public void reset() { |
| AndroidDataSource.State state = myDataSource.getState(); |
| myNameField.setText(StringUtil.notNullize(myDataSource.getName())); |
| |
| myInternalStorageRadioButton.setSelected(!state.external); |
| myExternalStorageRadioButton.setSelected(state.external); |
| |
| myPackageNameComboBox.getEditor().setItem(StringUtil.notNullize(state.packageName)); |
| myDataBaseComboBox.getEditor().setItem(StringUtil.notNullize(state.databaseName)); |
| } |
| |
| private void registerDeviceListener() { |
| AndroidDebugBridge.addDeviceChangeListener(myDeviceListener); |
| Disposer.register(this, new Disposable() { |
| @Override |
| public void dispose() { |
| AndroidDebugBridge.removeDeviceChangeListener(myDeviceListener); |
| } |
| }); |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| @Override |
| public void disposeUIResources() { |
| Disposer.dispose(this); |
| } |
| |
| @Nullable |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| return myNameField; |
| } |
| |
| @Nls |
| @Override |
| public String getDisplayName() { |
| return getNameValue(); |
| } |
| |
| private String getNameValue() { |
| return myNameField.getText().trim(); |
| } |
| |
| @Nullable |
| @Override |
| public String getHelpTopic() { |
| return null; // todo |
| } |
| |
| private void checkDriverPresence() { |
| final DatabaseDriver driver = myDataSource.getDatabaseDriver(); |
| if (driver != null && !driver.isDownloaded()) { |
| myController.showErrorNotification(this, |
| "SQLite driver missing", |
| "<font size=\"3\"><a href=\"create\">Download</a> SQLite driver files</font>", |
| new Runnable() { |
| @Override |
| public void run() { |
| driver.downloadDriver(myPanel, new Runnable() { |
| @Override |
| public void run() { |
| fireStateChanged(); |
| myController.showErrorNotification(AndroidDataSourcePropertiesDialog.this, null); |
| } |
| }); |
| } |
| }); |
| } |
| else { |
| myController.showErrorNotification(this, null); |
| } |
| } |
| |
| public boolean isModified() { |
| if (isNewDataSource()) return true; |
| AndroidDataSource tempDataSource = getTempDataSource(); |
| |
| if (!StringUtil.equals(tempDataSource.getName(), myDataSource.getName())) return true; |
| return !tempDataSource.equalConfiguration(myDataSource); |
| } |
| } |