OSDN Git Service

original
[gb-231r1-is01/GB_2.3_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / wizards / export / KeyCheckPage.java
diff --git a/sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java b/sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java
new file mode 100644 (file)
index 0000000..5c2a3cd
--- /dev/null
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.android.ide.eclipse.adt.internal.wizards.export;
+
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard.ExportWizardPage;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.forms.widgets.FormText;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableEntryException;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+
+/**
+ * Final page of the wizard that checks the key and ask for the ouput location.
+ */
+final class KeyCheckPage extends ExportWizardPage {
+
+    private final ExportWizard mWizard;
+    private PrivateKey mPrivateKey;
+    private X509Certificate mCertificate;
+    private Text mDestination;
+    private boolean mFatalSigningError;
+    private FormText mDetailText;
+    private ScrolledComposite mScrolledComposite;
+
+    private String mKeyDetails;
+    private String mDestinationDetails;
+
+    protected KeyCheckPage(ExportWizard wizard, String pageName) {
+        super(pageName);
+        mWizard = wizard;
+
+        setTitle("Destination and key/certificate checks");
+        setDescription(""); // TODO
+    }
+
+    public void createControl(Composite parent) {
+        setErrorMessage(null);
+        setMessage(null);
+
+        // build the ui.
+        Composite composite = new Composite(parent, SWT.NULL);
+        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+        GridLayout gl = new GridLayout(3, false);
+        gl.verticalSpacing *= 3;
+        composite.setLayout(gl);
+
+        GridData gd;
+
+        new Label(composite, SWT.NONE).setText("Destination APK file:");
+        mDestination = new Text(composite, SWT.BORDER);
+        mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        mDestination.addModifyListener(new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onDestinationChange(false /*forceDetailUpdate*/);
+            }
+        });
+        final Button browseButton = new Button(composite, SWT.PUSH);
+        browseButton.setText("Browse...");
+        browseButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
+
+                fileDialog.setText("Destination file name");
+                // get a default apk name based on the project
+                String filename = ProjectHelper.getApkFilename(mWizard.getProject(),
+                        null /*config*/);
+                fileDialog.setFileName(filename);
+
+                String saveLocation = fileDialog.open();
+                if (saveLocation != null) {
+                    mDestination.setText(saveLocation);
+                }
+            }
+        });
+
+        mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
+        mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
+        gd.horizontalSpan = 3;
+        mScrolledComposite.setExpandHorizontal(true);
+        mScrolledComposite.setExpandVertical(true);
+
+        mDetailText = new FormText(mScrolledComposite, SWT.NONE);
+        mScrolledComposite.setContent(mDetailText);
+
+        mScrolledComposite.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                updateScrolling();
+            }
+        });
+
+        setControl(composite);
+    }
+
+    @Override
+    void onShow() {
+        // fill the texts with information loaded from the project.
+        if ((mProjectDataChanged & DATA_PROJECT) != 0) {
+            // reset the destination from the content of the project
+            IProject project = mWizard.getProject();
+
+            String destination = ProjectHelper.loadStringProperty(project,
+                    ExportWizard.PROPERTY_DESTINATION);
+            if (destination != null) {
+                mDestination.setText(destination);
+            }
+        }
+
+        // if anything change we basically reload the data.
+        if (mProjectDataChanged != 0) {
+            mFatalSigningError = false;
+
+            // reset the wizard with no key/cert to make it not finishable, unless a valid
+            // key/cert is found.
+            mWizard.setSigningInfo(null, null);
+            mPrivateKey = null;
+            mCertificate = null;
+            mKeyDetails = null;
+
+            if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
+                int validity = mWizard.getValidity();
+                StringBuilder sb = new StringBuilder(
+                        String.format("<p>Certificate expires in %d years.</p>",
+                        validity));
+
+                if (validity < 25) {
+                    sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
+                    sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
+                    sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
+                    sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
+                    sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
+                }
+
+                mKeyDetails = sb.toString();
+            } else {
+                try {
+                    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+                    FileInputStream fis = new FileInputStream(mWizard.getKeystore());
+                    keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
+                    fis.close();
+                    PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
+                            mWizard.getKeyAlias(),
+                            new KeyStore.PasswordProtection(
+                                    mWizard.getKeyPassword().toCharArray()));
+
+                    if (entry != null) {
+                        mPrivateKey = entry.getPrivateKey();
+                        mCertificate = (X509Certificate)entry.getCertificate();
+                    } else {
+                        setErrorMessage("Unable to find key.");
+
+                        setPageComplete(false);
+                    }
+                } catch (FileNotFoundException e) {
+                    // this was checked at the first previous step and will not happen here, unless
+                    // the file was removed during the export wizard execution.
+                    onException(e);
+                } catch (KeyStoreException e) {
+                    onException(e);
+                } catch (NoSuchAlgorithmException e) {
+                    onException(e);
+                } catch (UnrecoverableEntryException e) {
+                    onException(e);
+                } catch (CertificateException e) {
+                    onException(e);
+                } catch (IOException e) {
+                    onException(e);
+                }
+
+                if (mPrivateKey != null && mCertificate != null) {
+                    Calendar expirationCalendar = Calendar.getInstance();
+                    expirationCalendar.setTime(mCertificate.getNotAfter());
+                    Calendar today = Calendar.getInstance();
+
+                    if (expirationCalendar.before(today)) {
+                        mKeyDetails = String.format(
+                                "<p>Certificate expired on %s</p>",
+                                mCertificate.getNotAfter().toString());
+
+                        // fatal error = nothing can make the page complete.
+                        mFatalSigningError = true;
+
+                        setErrorMessage("Certificate is expired.");
+                        setPageComplete(false);
+                    } else {
+                        // valid, key/cert: put it in the wizard so that it can be finished
+                        mWizard.setSigningInfo(mPrivateKey, mCertificate);
+
+                        StringBuilder sb = new StringBuilder(String.format(
+                                "<p>Certificate expires on %s.</p>",
+                                mCertificate.getNotAfter().toString()));
+
+                        int expirationYear = expirationCalendar.get(Calendar.YEAR);
+                        int thisYear = today.get(Calendar.YEAR);
+
+                        if (thisYear + 25 < expirationYear) {
+                            // do nothing
+                        } else {
+                            if (expirationYear == thisYear) {
+                                sb.append("<p>The certificate expires this year.</p>");
+                            } else {
+                                int count = expirationYear-thisYear;
+                                sb.append(String.format(
+                                        "<p>The Certificate expires in %1$s %2$s.</p>",
+                                        count, count == 1 ? "year" : "years"));
+                            }
+
+                            sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
+                            sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
+                            sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
+                            sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
+                            sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
+                        }
+
+                        mKeyDetails = sb.toString();
+                    }
+                } else {
+                    // fatal error = nothing can make the page complete.
+                    mFatalSigningError = true;
+                }
+            }
+        }
+
+        onDestinationChange(true /*forceDetailUpdate*/);
+    }
+
+    /**
+     * Callback for destination field edition
+     * @param forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal
+     * error has happened in the signing.
+     */
+    private void onDestinationChange(boolean forceDetailUpdate) {
+        if (mFatalSigningError == false) {
+            // reset messages for now.
+            setErrorMessage(null);
+            setMessage(null);
+
+            String path = mDestination.getText().trim();
+
+            if (path.length() == 0) {
+                setErrorMessage("Enter destination for the APK file.");
+                // reset canFinish in the wizard.
+                mWizard.resetDestination();
+                setPageComplete(false);
+                return;
+            }
+
+            File file = new File(path);
+            if (file.isDirectory()) {
+                setErrorMessage("Destination is a directory.");
+                // reset canFinish in the wizard.
+                mWizard.resetDestination();
+                setPageComplete(false);
+                return;
+            }
+
+            File parentFolder = file.getParentFile();
+            if (parentFolder == null || parentFolder.isDirectory() == false) {
+                setErrorMessage("Not a valid directory.");
+                // reset canFinish in the wizard.
+                mWizard.resetDestination();
+                setPageComplete(false);
+                return;
+            }
+
+            if (file.isFile()) {
+                mDestinationDetails = "<li>WARNING: destination file already exists</li>";
+                setMessage("Destination file already exists.", WARNING);
+            }
+
+            // no error, set the destination in the wizard.
+            mWizard.setDestination(file);
+            setPageComplete(true);
+
+            updateDetailText();
+        } else if (forceDetailUpdate) {
+            updateDetailText();
+        }
+    }
+
+    /**
+     * Updates the scrollbar to match the content of the {@link FormText} or the new size
+     * of the {@link ScrolledComposite}.
+     */
+    private void updateScrolling() {
+        if (mDetailText != null) {
+            Rectangle r = mScrolledComposite.getClientArea();
+            mScrolledComposite.setMinSize(mDetailText.computeSize(r.width, SWT.DEFAULT));
+            mScrolledComposite.layout();
+        }
+    }
+
+    private void updateDetailText() {
+        StringBuilder sb = new StringBuilder("<form>");
+        if (mKeyDetails != null) {
+            sb.append(mKeyDetails);
+        }
+
+        if (mDestinationDetails != null && mFatalSigningError == false) {
+            sb.append(mDestinationDetails);
+        }
+
+        sb.append("</form>");
+
+        mDetailText.setText(sb.toString(), true /* parseTags */,
+                true /* expandURLs */);
+
+        mDetailText.getParent().layout();
+
+        updateScrolling();
+    }
+
+    @Override
+    protected void onException(Throwable t) {
+        super.onException(t);
+
+        mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t));
+    }
+}