1 /* 2 * Copyright (C) 2007 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.preferences; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 21 import com.android.ide.eclipse.adt.internal.utils.FingerprintUtils; 22 import com.android.prefs.AndroidLocation.AndroidLocationException; 23 import com.android.sdklib.internal.build.DebugKeyProvider; 24 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException; 25 26 import org.eclipse.jface.preference.BooleanFieldEditor; 27 import org.eclipse.jface.preference.FieldEditorPreferencePage; 28 import org.eclipse.jface.preference.FileFieldEditor; 29 import org.eclipse.jface.preference.IPreferenceStore; 30 import org.eclipse.jface.preference.RadioGroupFieldEditor; 31 import org.eclipse.jface.preference.StringFieldEditor; 32 import org.eclipse.swt.widgets.Composite; 33 import org.eclipse.swt.widgets.Text; 34 import org.eclipse.ui.IWorkbench; 35 import org.eclipse.ui.IWorkbenchPreferencePage; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.security.GeneralSecurityException; 40 import java.security.PrivateKey; 41 import java.security.cert.Certificate; 42 import java.security.cert.X509Certificate; 43 import java.util.Date; 44 45 /** 46 * Preference page for build options. 47 * 48 */ 49 public class BuildPreferencePage extends FieldEditorPreferencePage implements 50 IWorkbenchPreferencePage { 51 52 private IPreferenceStore mPrefStore = null; 53 54 // default key store 55 private ReadOnlyFieldEditor mDefaultKeyStore = null; 56 private LabelField mDefaultFingerprintMd5 = null; 57 private LabelField mDefaultFingerprintSha1 = null; 58 59 // custom key store 60 private KeystoreFieldEditor mCustomKeyStore = null; 61 private LabelField mCustomFingerprintMd5 = null; 62 private LabelField mCustomFingerprintSha1 = null; 63 64 public BuildPreferencePage() { 65 super(GRID); 66 mPrefStore = AdtPlugin.getDefault().getPreferenceStore(); 67 setPreferenceStore(mPrefStore); 68 setDescription(Messages.BuildPreferencePage_Title); 69 } 70 71 @Override 72 protected void createFieldEditors() { 73 addField(new BooleanFieldEditor(AdtPrefs.PREFS_BUILD_RES_AUTO_REFRESH, 74 Messages.BuildPreferencePage_Auto_Refresh_Resources_on_Build, 75 getFieldEditorParent())); 76 77 addField(new BooleanFieldEditor(AdtPrefs.PREFS_BUILD_FORCE_ERROR_ON_NATIVELIB_IN_JAR, 78 "Force error when external jars contain native libraries", 79 getFieldEditorParent())); 80 81 addField(new BooleanFieldEditor(AdtPrefs.PREFS_BUILD_SKIP_POST_COMPILE_ON_FILE_SAVE, 82 "Skip packaging and dexing until export or launch. (Speeds up automatic builds on file save)", 83 getFieldEditorParent())); 84 85 RadioGroupFieldEditor rgfe = new RadioGroupFieldEditor( 86 AdtPrefs.PREFS_BUILD_VERBOSITY, 87 Messages.BuildPreferencePage_Build_Output, 1, new String[][] { 88 { Messages.BuildPreferencePage_Silent, BuildVerbosity.ALWAYS.name() }, 89 { Messages.BuildPreferencePage_Normal, BuildVerbosity.NORMAL.name() }, 90 { Messages.BuildPreferencePage_Verbose, BuildVerbosity.VERBOSE.name() } 91 }, 92 getFieldEditorParent(), true); 93 addField(rgfe); 94 95 // default debug keystore fingerprints 96 Fingerprints defaultFingerprints = getFingerprints( 97 mPrefStore.getString(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE)); 98 99 // default debug key store fields 100 mDefaultKeyStore = new ReadOnlyFieldEditor(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE, 101 Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()); 102 mDefaultFingerprintMd5 = new LabelField( 103 Messages.BuildPreferencePage_Default_Certificate_Fingerprint_MD5, 104 defaultFingerprints != null ? defaultFingerprints.md5 : "", 105 getFieldEditorParent()); 106 mDefaultFingerprintSha1 = new LabelField( 107 Messages.BuildPreferencePage_Default_Certificate_Fingerprint_SHA1, 108 defaultFingerprints != null ? defaultFingerprints.sha1 : "", 109 getFieldEditorParent()); 110 111 addField(mDefaultKeyStore); 112 addField(mDefaultFingerprintMd5); 113 addField(mDefaultFingerprintSha1); 114 115 // custom debug keystore fingerprints 116 Fingerprints customFingerprints = null; 117 118 String customDebugKeystorePath = mPrefStore.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE); 119 if (new File(customDebugKeystorePath).isFile()) { 120 customFingerprints = getFingerprints(customDebugKeystorePath); 121 } else { 122 // file does not exist. 123 setErrorMessage("Not a valid keystore path."); 124 } 125 126 // custom debug key store fields 127 mCustomKeyStore = new KeystoreFieldEditor(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE, 128 Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()); 129 mCustomFingerprintMd5 = new LabelField( 130 Messages.BuildPreferencePage_Default_Certificate_Fingerprint_MD5, 131 customFingerprints != null ? customFingerprints.md5 : "", 132 getFieldEditorParent()); 133 mCustomFingerprintSha1 = new LabelField( 134 Messages.BuildPreferencePage_Default_Certificate_Fingerprint_SHA1, 135 customFingerprints != null ? customFingerprints.sha1 : "", 136 getFieldEditorParent()); 137 138 // set fingerprint fields 139 mCustomKeyStore.setFingerprintMd5Field(mCustomFingerprintMd5); 140 mCustomKeyStore.setFingerprintSha1Field(mCustomFingerprintSha1); 141 142 addField(mCustomKeyStore); 143 addField(mCustomFingerprintMd5); 144 addField(mCustomFingerprintSha1); 145 } 146 147 /** 148 * MD5 & SHA1 fingerprints. 149 */ 150 private static class Fingerprints { 151 final String md5; 152 final String sha1; 153 154 Fingerprints(String md5Val, String sha1Val) { 155 md5 = md5Val; 156 sha1 = sha1Val; 157 } 158 } 159 160 private Fingerprints getFingerprints(String keystorePath) { 161 // attempt to load the debug key. 162 try { 163 DebugKeyProvider keyProvider = new DebugKeyProvider(keystorePath, 164 null /* storeType */, null /* key gen output */); 165 166 return new Fingerprints( 167 FingerprintUtils.getFingerprint(keyProvider.getCertificate(), "MD5"), 168 FingerprintUtils.getFingerprint(keyProvider.getCertificate(), "SHA1")); 169 } catch (GeneralSecurityException e) { 170 setErrorMessage(e.getMessage()); 171 } catch (IOException e) { 172 setErrorMessage(e.getMessage()); 173 } catch (KeytoolException e) { 174 setErrorMessage(e.getMessage()); 175 } catch (AndroidLocationException e) { 176 setErrorMessage(e.getMessage()); 177 } 178 return null; 179 } 180 181 /* 182 * (non-Javadoc) 183 * 184 * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) 185 */ 186 @Override 187 public void init(IWorkbench workbench) { 188 } 189 190 /* 191 * (non-Javadoc) 192 * 193 * @see org.eclipse.jface.preference.FieldEditorPreferencePage#performDefaults 194 * (org.eclipse.jface.preference.PreferencePage#performDefaults) 195 */ 196 @Override 197 protected void performDefaults() { 198 super.performDefaults(); 199 200 // restore the default key store fingerprints 201 Fingerprints defaultFingerprints = getFingerprints(mPrefStore 202 .getString(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE)); 203 mDefaultFingerprintMd5.setStringValue(defaultFingerprints.md5); 204 mDefaultFingerprintSha1.setStringValue(defaultFingerprints.sha1); 205 206 // set custom fingerprint fields to blank 207 mCustomFingerprintMd5.setStringValue(""); 208 mCustomFingerprintSha1.setStringValue(""); 209 } 210 211 /** 212 * A read-only string field editor. 213 */ 214 private static class ReadOnlyFieldEditor extends StringFieldEditor { 215 216 public ReadOnlyFieldEditor(String name, String labelText, Composite parent) { 217 super(name, labelText, parent); 218 } 219 220 @Override 221 protected void createControl(Composite parent) { 222 super.createControl(parent); 223 224 Text control = getTextControl(); 225 control.setEditable(false); 226 } 227 } 228 229 /** 230 * A read-only string field. 231 */ 232 private static class LabelField extends StringFieldEditor { 233 private String text; 234 235 public LabelField(String labelText, String value, Composite parent) { 236 super("", labelText, parent); 237 text = value; 238 } 239 240 @Override 241 protected void createControl(Composite parent) { 242 super.createControl(parent); 243 244 Text control = getTextControl(); 245 control.setEditable(false); 246 } 247 248 @Override 249 protected void doLoad() { 250 setStringValue(text); 251 } 252 253 @Override 254 protected void doStore() { 255 // Do nothing 256 } 257 } 258 259 /** 260 * Custom {@link FileFieldEditor} that checks that the keystore is valid. 261 */ 262 private static class KeystoreFieldEditor extends FileFieldEditor { 263 private StringFieldEditor fingerprintMd5 = null; 264 private StringFieldEditor fingerprintSha1 = null; 265 266 public void setFingerprintMd5Field(StringFieldEditor field) { 267 fingerprintMd5 = field; 268 } 269 270 public void setFingerprintSha1Field(StringFieldEditor field) { 271 fingerprintSha1 = field; 272 } 273 274 public KeystoreFieldEditor(String name, String label, Composite parent) { 275 super(name, label, parent); 276 setValidateStrategy(VALIDATE_ON_KEY_STROKE); 277 } 278 279 @Override 280 protected boolean checkState() { 281 String fileName = getTextControl().getText(); 282 fileName = fileName.trim(); 283 284 if (fingerprintMd5 != null) { 285 fingerprintMd5.setStringValue(""); 286 } 287 288 if (fingerprintSha1 != null) { 289 fingerprintSha1.setStringValue(""); 290 } 291 292 // empty values are considered ok. 293 if (fileName.length() > 0) { 294 File file = new File(fileName); 295 if (file.isFile()) { 296 // attempt to load the debug key. 297 try { 298 DebugKeyProvider provider = new DebugKeyProvider(fileName, 299 null /* storeType */, null /* key gen output */); 300 PrivateKey key = provider.getDebugKey(); 301 X509Certificate certificate = (X509Certificate)provider.getCertificate(); 302 303 if (key == null || certificate == null) { 304 showErrorMessage("Unable to find debug key in keystore!"); 305 return false; 306 } 307 308 if (fingerprintMd5 != null) { 309 fingerprintMd5.setStringValue( 310 FingerprintUtils.getFingerprint(certificate, "MD5")); 311 } 312 313 if (fingerprintSha1 != null) { 314 fingerprintSha1.setStringValue( 315 FingerprintUtils.getFingerprint(certificate, "SHA1")); 316 } 317 318 Date today = new Date(); 319 if (certificate.getNotAfter().compareTo(today) < 0) { 320 showErrorMessage("Certificate is expired!"); 321 return false; 322 } 323 324 if (certificate.getNotBefore().compareTo(today) > 0) { 325 showErrorMessage("Certificate validity is in the future!"); 326 return false; 327 } 328 329 // we're good! 330 clearErrorMessage(); 331 return true; 332 } catch (GeneralSecurityException e) { 333 handleException(e); 334 return false; 335 } catch (IOException e) { 336 handleException(e); 337 return false; 338 } catch (KeytoolException e) { 339 handleException(e); 340 return false; 341 } catch (AndroidLocationException e) { 342 handleException(e); 343 return false; 344 } 345 346 347 } else { 348 // file does not exist. 349 showErrorMessage("Not a valid keystore path."); 350 return false; // Apply/OK must be disabled 351 } 352 } 353 354 clearErrorMessage(); 355 return true; 356 } 357 358 @Override 359 public Text getTextControl(Composite parent) { 360 setValidateStrategy(VALIDATE_ON_KEY_STROKE); 361 return super.getTextControl(parent); 362 } 363 364 /** 365 * Set the error message from a {@link Throwable}. If the exception has no message, try 366 * to get the message from the cause. 367 * @param t the Throwable. 368 */ 369 private void handleException(Throwable t) { 370 String msg = t.getMessage(); 371 if (msg == null) { 372 Throwable cause = t.getCause(); 373 if (cause != null) { 374 handleException(cause); 375 } else { 376 setErrorMessage("Uknown error when getting the debug key!"); 377 } 378 379 return; 380 } 381 382 // valid text, display it. 383 showErrorMessage(msg); 384 } 385 } 386 } 387