1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.geckoview_example; 7 8 import android.annotation.TargetApi; 9 import android.app.Activity; 10 import android.app.AlertDialog; 11 import android.content.ActivityNotFoundException; 12 import android.content.ClipData; 13 import android.content.Context; 14 import android.content.DialogInterface; 15 import android.content.Intent; 16 import android.content.res.TypedArray; 17 import android.graphics.Color; 18 import android.graphics.PorterDuff; 19 import android.net.Uri; 20 import android.os.Build; 21 import android.text.InputType; 22 import android.text.format.DateFormat; 23 import android.util.Log; 24 import android.view.InflateException; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.AdapterView; 29 import android.widget.ArrayAdapter; 30 import android.widget.CheckBox; 31 import android.widget.CheckedTextView; 32 import android.widget.CompoundButton; 33 import android.widget.DatePicker; 34 import android.widget.EditText; 35 import android.widget.FrameLayout; 36 import android.widget.ImageView; 37 import android.widget.LinearLayout; 38 import android.widget.ListView; 39 import android.widget.ScrollView; 40 import android.widget.Spinner; 41 import android.widget.TextView; 42 import android.widget.TimePicker; 43 44 import java.text.ParseException; 45 import java.text.SimpleDateFormat; 46 import java.util.ArrayList; 47 import java.util.Calendar; 48 import java.util.Date; 49 import java.util.Locale; 50 51 import org.mozilla.geckoview.GeckoSession; 52 import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource; 53 54 final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate { 55 protected static final String LOGTAG = "BasicGeckoViewPrompt"; 56 57 private final Activity mActivity; 58 public int filePickerRequestCode = 1; 59 private int mFileType; 60 private FileCallback mFileCallback; 61 BasicGeckoViewPrompt(final Activity activity)62 public BasicGeckoViewPrompt(final Activity activity) { 63 mActivity = activity; 64 } 65 addCheckbox(final AlertDialog.Builder builder, ViewGroup parent, final AlertCallback callback)66 private AlertDialog.Builder addCheckbox(final AlertDialog.Builder builder, 67 ViewGroup parent, 68 final AlertCallback callback) { 69 if (!callback.hasCheckbox()) { 70 return builder; 71 } 72 final CheckBox checkbox = new CheckBox(builder.getContext()); 73 if (callback.getCheckboxMessage() != null) { 74 checkbox.setText(callback.getCheckboxMessage()); 75 } 76 checkbox.setChecked(callback.getCheckboxValue()); 77 checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 78 @Override 79 public void onCheckedChanged(final CompoundButton button, 80 final boolean checked) { 81 callback.setCheckboxValue(checked); 82 } 83 }); 84 if (parent == null) { 85 final int padding = getViewPadding(builder); 86 parent = new FrameLayout(builder.getContext()); 87 parent.setPadding(/* left */ padding, /* top */ 0, 88 /* right */ padding, /* bottom */ 0); 89 builder.setView(parent); 90 } 91 parent.addView(checkbox); 92 return builder; 93 } 94 onAlert(final GeckoSession session, final String title, final String msg, final AlertCallback callback)95 public void onAlert(final GeckoSession session, final String title, final String msg, 96 final AlertCallback callback) { 97 final Activity activity = mActivity; 98 if (activity == null) { 99 callback.dismiss(); 100 return; 101 } 102 final AlertDialog.Builder builder = new AlertDialog.Builder(activity) 103 .setTitle(title) 104 .setMessage(msg) 105 .setPositiveButton(android.R.string.ok, /* onClickListener */ null); 106 createStandardDialog(addCheckbox(builder, /* parent */ null, callback), 107 callback).show(); 108 } 109 onButtonPrompt(final GeckoSession session, final String title, final String msg, final String[] btnMsg, final ButtonCallback callback)110 public void onButtonPrompt(final GeckoSession session, final String title, 111 final String msg, final String[] btnMsg, 112 final ButtonCallback callback) { 113 final Activity activity = mActivity; 114 if (activity == null) { 115 callback.dismiss(); 116 return; 117 } 118 final AlertDialog.Builder builder = new AlertDialog.Builder(activity) 119 .setTitle(title) 120 .setMessage(msg); 121 final DialogInterface.OnClickListener listener = 122 new DialogInterface.OnClickListener() { 123 @Override 124 public void onClick(final DialogInterface dialog, final int which) { 125 if (which == DialogInterface.BUTTON_POSITIVE) { 126 callback.confirm(BUTTON_TYPE_POSITIVE); 127 } else if (which == DialogInterface.BUTTON_NEUTRAL) { 128 callback.confirm(BUTTON_TYPE_NEUTRAL); 129 } else if (which == DialogInterface.BUTTON_NEGATIVE) { 130 callback.confirm(BUTTON_TYPE_NEGATIVE); 131 } else { 132 callback.dismiss(); 133 } 134 } 135 }; 136 if (btnMsg[BUTTON_TYPE_POSITIVE] != null) { 137 builder.setPositiveButton(btnMsg[BUTTON_TYPE_POSITIVE], listener); 138 } 139 if (btnMsg[BUTTON_TYPE_NEUTRAL] != null) { 140 builder.setNeutralButton(btnMsg[BUTTON_TYPE_NEUTRAL], listener); 141 } 142 if (btnMsg[BUTTON_TYPE_NEGATIVE] != null) { 143 builder.setNegativeButton(btnMsg[BUTTON_TYPE_NEGATIVE], listener); 144 } 145 createStandardDialog(addCheckbox(builder, /* parent */ null, callback), 146 callback).show(); 147 } 148 getViewPadding(final AlertDialog.Builder builder)149 private int getViewPadding(final AlertDialog.Builder builder) { 150 final TypedArray attr = builder.getContext().obtainStyledAttributes( 151 new int[] { android.R.attr.listPreferredItemPaddingLeft }); 152 final int padding = attr.getDimensionPixelSize(0, 1); 153 attr.recycle(); 154 return padding; 155 } 156 addStandardLayout(final AlertDialog.Builder builder, final String title, final String msg)157 private LinearLayout addStandardLayout(final AlertDialog.Builder builder, 158 final String title, final String msg) { 159 final ScrollView scrollView = new ScrollView(builder.getContext()); 160 final LinearLayout container = new LinearLayout(builder.getContext()); 161 final int horizontalPadding = getViewPadding(builder); 162 final int verticalPadding = (msg == null || msg.isEmpty()) ? horizontalPadding : 0; 163 container.setOrientation(LinearLayout.VERTICAL); 164 container.setPadding(/* left */ horizontalPadding, /* top */ verticalPadding, 165 /* right */ horizontalPadding, /* bottom */ verticalPadding); 166 scrollView.addView(container); 167 builder.setTitle(title) 168 .setMessage(msg) 169 .setView(scrollView); 170 return container; 171 } 172 createStandardDialog(final AlertDialog.Builder builder, final AlertCallback callback)173 private AlertDialog createStandardDialog(final AlertDialog.Builder builder, 174 final AlertCallback callback) { 175 final AlertDialog dialog = builder.create(); 176 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 177 @Override 178 public void onDismiss(final DialogInterface dialog) { 179 callback.dismiss(); 180 } 181 }); 182 return dialog; 183 } 184 onTextPrompt(final GeckoSession session, final String title, final String msg, final String value, final TextCallback callback)185 public void onTextPrompt(final GeckoSession session, final String title, 186 final String msg, final String value, 187 final TextCallback callback) { 188 final Activity activity = mActivity; 189 if (activity == null) { 190 callback.dismiss(); 191 return; 192 } 193 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 194 final LinearLayout container = addStandardLayout(builder, title, msg); 195 final EditText editText = new EditText(builder.getContext()); 196 editText.setText(value); 197 container.addView(editText); 198 199 builder.setNegativeButton(android.R.string.cancel, /* listener */ null) 200 .setPositiveButton(android.R.string.ok, 201 new DialogInterface.OnClickListener() { 202 @Override 203 public void onClick(final DialogInterface dialog, final int which) { 204 callback.confirm(editText.getText().toString()); 205 } 206 }); 207 208 createStandardDialog(addCheckbox(builder, container, callback), callback).show(); 209 } 210 onAuthPrompt(final GeckoSession session, final String title, final String msg, final AuthOptions options, final AuthCallback callback)211 public void onAuthPrompt(final GeckoSession session, final String title, 212 final String msg, final AuthOptions options, 213 final AuthCallback callback) { 214 final Activity activity = mActivity; 215 if (activity == null) { 216 callback.dismiss(); 217 return; 218 } 219 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 220 final LinearLayout container = addStandardLayout(builder, title, msg); 221 222 final int flags = options.flags; 223 final int level = options.level; 224 final EditText username; 225 if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) { 226 username = new EditText(builder.getContext()); 227 username.setHint(R.string.username); 228 username.setText(options.username); 229 container.addView(username); 230 } else { 231 username = null; 232 } 233 234 final EditText password = new EditText(builder.getContext()); 235 password.setHint(R.string.password); 236 password.setText(options.password); 237 password.setInputType(InputType.TYPE_CLASS_TEXT | 238 InputType.TYPE_TEXT_VARIATION_PASSWORD); 239 container.addView(password); 240 241 if (level != AuthOptions.AUTH_LEVEL_NONE) { 242 final ImageView secure = new ImageView(builder.getContext()); 243 secure.setImageResource(android.R.drawable.ic_lock_lock); 244 container.addView(secure); 245 } 246 247 builder.setNegativeButton(android.R.string.cancel, /* listener */ null) 248 .setPositiveButton(android.R.string.ok, 249 new DialogInterface.OnClickListener() { 250 @Override 251 public void onClick(final DialogInterface dialog, final int which) { 252 if ((flags & AuthOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) { 253 callback.confirm(username.getText().toString(), 254 password.getText().toString()); 255 } else { 256 callback.confirm(password.getText().toString()); 257 } 258 } 259 }); 260 createStandardDialog(addCheckbox(builder, container, callback), callback).show(); 261 } 262 263 private static class ModifiableChoice { 264 public boolean modifiableSelected; 265 public String modifiableLabel; 266 public final Choice choice; 267 ModifiableChoice(Choice c)268 public ModifiableChoice(Choice c) { 269 choice = c; 270 modifiableSelected = choice.selected; 271 modifiableLabel = choice.label; 272 } 273 } 274 addChoiceItems(final int type, final ArrayAdapter<ModifiableChoice> list, final Choice[] items, final String indent)275 private void addChoiceItems(final int type, final ArrayAdapter<ModifiableChoice> list, 276 final Choice[] items, final String indent) { 277 if (type == Choice.CHOICE_TYPE_MENU) { 278 for (final Choice item : items) { 279 list.add(new ModifiableChoice(item)); 280 } 281 return; 282 } 283 284 for (final Choice item : items) { 285 final ModifiableChoice modItem = new ModifiableChoice(item); 286 287 final Choice[] children = item.items; 288 289 if (indent != null && children == null) { 290 modItem.modifiableLabel = indent + modItem.modifiableLabel; 291 } 292 list.add(modItem); 293 294 if (children != null) { 295 final String newIndent; 296 if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MULTIPLE) { 297 newIndent = (indent != null) ? indent + '\t' : "\t"; 298 } else { 299 newIndent = null; 300 } 301 addChoiceItems(type, list, children, newIndent); 302 } 303 } 304 } 305 onChoicePrompt(final GeckoSession session, final String title, final String msg, final int type, final Choice[] choices, final ChoiceCallback callback)306 public void onChoicePrompt(final GeckoSession session, final String title, 307 final String msg, final int type, 308 final Choice[] choices, final ChoiceCallback callback) { 309 final Activity activity = mActivity; 310 if (activity == null) { 311 callback.dismiss(); 312 return; 313 } 314 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 315 addStandardLayout(builder, title, msg); 316 317 final ListView list = new ListView(builder.getContext()); 318 if (type == Choice.CHOICE_TYPE_MULTIPLE) { 319 list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 320 } 321 322 final ArrayAdapter<ModifiableChoice> adapter = new ArrayAdapter<ModifiableChoice>( 323 builder.getContext(), android.R.layout.simple_list_item_1) { 324 private static final int TYPE_MENU_ITEM = 0; 325 private static final int TYPE_MENU_CHECK = 1; 326 private static final int TYPE_SEPARATOR = 2; 327 private static final int TYPE_GROUP = 3; 328 private static final int TYPE_SINGLE = 4; 329 private static final int TYPE_MULTIPLE = 5; 330 private static final int TYPE_COUNT = 6; 331 332 private LayoutInflater mInflater; 333 private View mSeparator; 334 335 @Override 336 public int getViewTypeCount() { 337 return TYPE_COUNT; 338 } 339 340 @Override 341 public int getItemViewType(final int position) { 342 final ModifiableChoice item = getItem(position); 343 if (item.choice.separator) { 344 return TYPE_SEPARATOR; 345 } else if (type == Choice.CHOICE_TYPE_MENU) { 346 return item.modifiableSelected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM; 347 } else if (item.choice.items != null) { 348 return TYPE_GROUP; 349 } else if (type == Choice.CHOICE_TYPE_SINGLE) { 350 return TYPE_SINGLE; 351 } else if (type == Choice.CHOICE_TYPE_MULTIPLE) { 352 return TYPE_MULTIPLE; 353 } else { 354 throw new UnsupportedOperationException(); 355 } 356 } 357 358 @Override 359 public boolean isEnabled(final int position) { 360 final ModifiableChoice item = getItem(position); 361 return !item.choice.separator && !item.choice.disabled && 362 ((type != Choice.CHOICE_TYPE_SINGLE && type != Choice.CHOICE_TYPE_MULTIPLE) || 363 item.choice.items != null); 364 } 365 366 @Override 367 public View getView(final int position, View view, 368 final ViewGroup parent) { 369 final int itemType = getItemViewType(position); 370 final int layoutId; 371 if (itemType == TYPE_SEPARATOR) { 372 if (mSeparator == null) { 373 mSeparator = new View(getContext()); 374 mSeparator.setLayoutParams(new ListView.LayoutParams( 375 ViewGroup.LayoutParams.MATCH_PARENT, 2, itemType)); 376 final TypedArray attr = getContext().obtainStyledAttributes( 377 new int[] { android.R.attr.listDivider }); 378 mSeparator.setBackgroundResource(attr.getResourceId(0, 0)); 379 attr.recycle(); 380 } 381 return mSeparator; 382 } else if (itemType == TYPE_MENU_ITEM) { 383 layoutId = android.R.layout.simple_list_item_1; 384 } else if (itemType == TYPE_MENU_CHECK) { 385 layoutId = android.R.layout.simple_list_item_checked; 386 } else if (itemType == TYPE_GROUP) { 387 layoutId = android.R.layout.preference_category; 388 } else if (itemType == TYPE_SINGLE) { 389 layoutId = android.R.layout.simple_list_item_single_choice; 390 } else if (itemType == TYPE_MULTIPLE) { 391 layoutId = android.R.layout.simple_list_item_multiple_choice; 392 } else { 393 throw new UnsupportedOperationException(); 394 } 395 396 if (view == null) { 397 if (mInflater == null) { 398 mInflater = LayoutInflater.from(builder.getContext()); 399 } 400 view = mInflater.inflate(layoutId, parent, false); 401 } 402 403 final ModifiableChoice item = getItem(position); 404 final TextView text = (TextView) view; 405 text.setEnabled(!item.choice.disabled); 406 text.setText(item.modifiableLabel); 407 if (view instanceof CheckedTextView) { 408 final boolean selected = item.modifiableSelected; 409 if (itemType == TYPE_MULTIPLE) { 410 list.setItemChecked(position, selected); 411 } else { 412 ((CheckedTextView) view).setChecked(selected); 413 } 414 } 415 return view; 416 } 417 }; 418 addChoiceItems(type, adapter, choices, /* indent */ null); 419 420 list.setAdapter(adapter); 421 builder.setView(list); 422 423 final AlertDialog dialog; 424 if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MENU) { 425 dialog = createStandardDialog(builder, callback); 426 list.setOnItemClickListener(new AdapterView.OnItemClickListener() { 427 @Override 428 public void onItemClick(final AdapterView<?> parent, final View v, 429 final int position, final long id) { 430 final ModifiableChoice item = adapter.getItem(position); 431 if (type == Choice.CHOICE_TYPE_MENU) { 432 final Choice[] children = item.choice.items; 433 if (children != null) { 434 // Show sub-menu. 435 dialog.setOnDismissListener(null); 436 dialog.dismiss(); 437 onChoicePrompt(session, item.modifiableLabel, /* msg */ null, 438 type, children, callback); 439 return; 440 } 441 } 442 callback.confirm(item.choice); 443 dialog.dismiss(); 444 } 445 }); 446 } else if (type == Choice.CHOICE_TYPE_MULTIPLE) { 447 list.setOnItemClickListener(new AdapterView.OnItemClickListener() { 448 @Override 449 public void onItemClick(final AdapterView<?> parent, final View v, 450 final int position, final long id) { 451 final ModifiableChoice item = adapter.getItem(position); 452 item.modifiableSelected = ((CheckedTextView) v).isChecked(); 453 } 454 }); 455 builder.setNegativeButton(android.R.string.cancel, /* listener */ null) 456 .setPositiveButton(android.R.string.ok, 457 new DialogInterface.OnClickListener() { 458 @Override 459 public void onClick(final DialogInterface dialog, 460 final int which) { 461 final int len = adapter.getCount(); 462 ArrayList<String> items = new ArrayList<>(len); 463 for (int i = 0; i < len; i++) { 464 final ModifiableChoice item = adapter.getItem(i); 465 if (item.modifiableSelected) { 466 items.add(item.choice.id); 467 } 468 } 469 callback.confirm(items.toArray(new String[items.size()])); 470 } 471 }); 472 dialog = createStandardDialog(builder, callback); 473 } else { 474 throw new UnsupportedOperationException(); 475 } 476 dialog.show(); 477 } 478 parseColor(final String value, final int def)479 private static int parseColor(final String value, final int def) { 480 try { 481 return Color.parseColor(value); 482 } catch (final IllegalArgumentException e) { 483 return def; 484 } 485 } 486 onColorPrompt(final GeckoSession session, final String title, final String value, final TextCallback callback)487 public void onColorPrompt(final GeckoSession session, final String title, 488 final String value, final TextCallback callback) 489 { 490 final Activity activity = mActivity; 491 if (activity == null) { 492 callback.dismiss(); 493 return; 494 } 495 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 496 addStandardLayout(builder, title, /* msg */ null); 497 498 final int initial = parseColor(value, /* def */ 0); 499 final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>( 500 builder.getContext(), android.R.layout.simple_list_item_1) { 501 private LayoutInflater mInflater; 502 503 @Override 504 public int getViewTypeCount() { 505 return 2; 506 } 507 508 @Override 509 public int getItemViewType(final int position) { 510 return (getItem(position) == initial) ? 1 : 0; 511 } 512 513 @Override 514 public View getView(final int position, View view, 515 final ViewGroup parent) { 516 if (mInflater == null) { 517 mInflater = LayoutInflater.from(builder.getContext()); 518 } 519 final int color = getItem(position); 520 if (view == null) { 521 view = mInflater.inflate((color == initial) ? 522 android.R.layout.simple_list_item_checked : 523 android.R.layout.simple_list_item_1, parent, false); 524 } 525 view.setBackgroundResource(android.R.drawable.editbox_background); 526 view.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY); 527 return view; 528 } 529 }; 530 531 adapter.addAll(0xffff4444 /* holo_red_light */, 532 0xffcc0000 /* holo_red_dark */, 533 0xffffbb33 /* holo_orange_light */, 534 0xffff8800 /* holo_orange_dark */, 535 0xff99cc00 /* holo_green_light */, 536 0xff669900 /* holo_green_dark */, 537 0xff33b5e5 /* holo_blue_light */, 538 0xff0099cc /* holo_blue_dark */, 539 0xffaa66cc /* holo_purple */, 540 0xffffffff /* white */, 541 0xffaaaaaa /* lighter_gray */, 542 0xff555555 /* darker_gray */, 543 0xff000000 /* black */); 544 545 final ListView list = new ListView(builder.getContext()); 546 list.setAdapter(adapter); 547 builder.setView(list); 548 549 final AlertDialog dialog = createStandardDialog(builder, callback); 550 list.setOnItemClickListener(new AdapterView.OnItemClickListener() { 551 @Override 552 public void onItemClick(final AdapterView<?> parent, final View v, 553 final int position, final long id) { 554 callback.confirm(String.format("#%06x", 0xffffff & adapter.getItem(position))); 555 dialog.dismiss(); 556 } 557 }); 558 dialog.show(); 559 } 560 parseDate(final SimpleDateFormat formatter, final String value, final boolean defaultToNow)561 private static Date parseDate(final SimpleDateFormat formatter, 562 final String value, 563 final boolean defaultToNow) { 564 try { 565 if (value != null && !value.isEmpty()) { 566 return formatter.parse(value); 567 } 568 } catch (final ParseException e) { 569 } 570 return defaultToNow ? new Date() : null; 571 } 572 573 @SuppressWarnings("deprecation") setTimePickerTime(final TimePicker picker, final Calendar cal)574 private static void setTimePickerTime(final TimePicker picker, final Calendar cal) { 575 if (Build.VERSION.SDK_INT >= 23) { 576 picker.setHour(cal.get(Calendar.HOUR_OF_DAY)); 577 picker.setMinute(cal.get(Calendar.MINUTE)); 578 } else { 579 picker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY)); 580 picker.setCurrentMinute(cal.get(Calendar.MINUTE)); 581 } 582 } 583 584 @SuppressWarnings("deprecation") setCalendarTime(final Calendar cal, final TimePicker picker)585 private static void setCalendarTime(final Calendar cal, final TimePicker picker) { 586 if (Build.VERSION.SDK_INT >= 23) { 587 cal.set(Calendar.HOUR_OF_DAY, picker.getHour()); 588 cal.set(Calendar.MINUTE, picker.getMinute()); 589 } else { 590 cal.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour()); 591 cal.set(Calendar.MINUTE, picker.getCurrentMinute()); 592 } 593 } 594 onDateTimePrompt(final GeckoSession session, final String title, final int type, final String value, final String min, final String max, final TextCallback callback)595 public void onDateTimePrompt(final GeckoSession session, final String title, 596 final int type, final String value, final String min, 597 final String max, final TextCallback callback) { 598 final Activity activity = mActivity; 599 if (activity == null) { 600 callback.dismiss(); 601 return; 602 } 603 final String format; 604 if (type == DATETIME_TYPE_DATE) { 605 format = "yyyy-MM-dd"; 606 } else if (type == DATETIME_TYPE_MONTH) { 607 format = "yyyy-MM"; 608 } else if (type == DATETIME_TYPE_WEEK) { 609 format = "yyyy-'W'ww"; 610 } else if (type == DATETIME_TYPE_TIME) { 611 format = "HH:mm"; 612 } else if (type == DATETIME_TYPE_DATETIME_LOCAL) { 613 format = "yyyy-MM-dd'T'HH:mm"; 614 } else { 615 throw new UnsupportedOperationException(); 616 } 617 618 final SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ROOT); 619 final Date minDate = parseDate(formatter, min, /* defaultToNow */ false); 620 final Date maxDate = parseDate(formatter, max, /* defaultToNow */ false); 621 final Date date = parseDate(formatter, value, /* defaultToNow */ true); 622 final Calendar cal = formatter.getCalendar(); 623 cal.setTime(date); 624 625 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 626 final LayoutInflater inflater = LayoutInflater.from(builder.getContext()); 627 final DatePicker datePicker; 628 if (type == DATETIME_TYPE_DATE || type == DATETIME_TYPE_MONTH || 629 type == DATETIME_TYPE_WEEK || type == DATETIME_TYPE_DATETIME_LOCAL) { 630 final int resId = builder.getContext().getResources().getIdentifier( 631 "date_picker_dialog", "layout", "android"); 632 DatePicker picker = null; 633 if (resId != 0) { 634 try { 635 picker = (DatePicker) inflater.inflate(resId, /* root */ null); 636 } catch (final ClassCastException|InflateException e) { 637 } 638 } 639 if (picker == null) { 640 picker = new DatePicker(builder.getContext()); 641 } 642 picker.init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 643 cal.get(Calendar.DAY_OF_MONTH), /* listener */ null); 644 if (minDate != null) { 645 picker.setMinDate(minDate.getTime()); 646 } 647 if (maxDate != null) { 648 picker.setMaxDate(maxDate.getTime()); 649 } 650 datePicker = picker; 651 } else { 652 datePicker = null; 653 } 654 655 final TimePicker timePicker; 656 if (type == DATETIME_TYPE_TIME || type == DATETIME_TYPE_DATETIME_LOCAL) { 657 final int resId = builder.getContext().getResources().getIdentifier( 658 "time_picker_dialog", "layout", "android"); 659 TimePicker picker = null; 660 if (resId != 0) { 661 try { 662 picker = (TimePicker) inflater.inflate(resId, /* root */ null); 663 } catch (final ClassCastException|InflateException e) { 664 } 665 } 666 if (picker == null) { 667 picker = new TimePicker(builder.getContext()); 668 } 669 setTimePickerTime(picker, cal); 670 picker.setIs24HourView(DateFormat.is24HourFormat(builder.getContext())); 671 timePicker = picker; 672 } else { 673 timePicker = null; 674 } 675 676 final LinearLayout container = addStandardLayout(builder, title, /* msg */ null); 677 container.setPadding(/* left */ 0, /* top */ 0, /* right */ 0, /* bottom */ 0); 678 if (datePicker != null) { 679 container.addView(datePicker); 680 } 681 if (timePicker != null) { 682 container.addView(timePicker); 683 } 684 685 final DialogInterface.OnClickListener listener = 686 new DialogInterface.OnClickListener() { 687 @Override 688 public void onClick(final DialogInterface dialog, final int which) { 689 if (which == DialogInterface.BUTTON_NEUTRAL) { 690 // Clear 691 callback.confirm(""); 692 return; 693 } 694 if (datePicker != null) { 695 cal.set(datePicker.getYear(), datePicker.getMonth(), 696 datePicker.getDayOfMonth()); 697 } 698 if (timePicker != null) { 699 setCalendarTime(cal, timePicker); 700 } 701 callback.confirm(formatter.format(cal.getTime())); 702 } 703 }; 704 builder.setNegativeButton(android.R.string.cancel, /* listener */ null) 705 .setNeutralButton(R.string.clear_field, listener) 706 .setPositiveButton(android.R.string.ok, listener); 707 createStandardDialog(builder, callback).show(); 708 } 709 710 @TargetApi(19) onFilePrompt(GeckoSession session, String title, int type, String[] mimeTypes, FileCallback callback)711 public void onFilePrompt(GeckoSession session, String title, int type, 712 String[] mimeTypes, FileCallback callback) 713 { 714 final Activity activity = mActivity; 715 if (activity == null) { 716 callback.dismiss(); 717 return; 718 } 719 720 // Merge all given MIME types into one, using wildcard if needed. 721 String mimeType = null; 722 String mimeSubtype = null; 723 for (final String rawType : mimeTypes) { 724 final String normalizedType = rawType.trim().toLowerCase(Locale.ROOT); 725 final int len = normalizedType.length(); 726 int slash = normalizedType.indexOf('/'); 727 if (slash < 0) { 728 slash = len; 729 } 730 final String newType = normalizedType.substring(0, slash); 731 final String newSubtype = normalizedType.substring(Math.min(slash + 1, len)); 732 if (mimeType == null) { 733 mimeType = newType; 734 } else if (!mimeType.equals(newType)) { 735 mimeType = "*"; 736 } 737 if (mimeSubtype == null) { 738 mimeSubtype = newSubtype; 739 } else if (!mimeSubtype.equals(newSubtype)) { 740 mimeSubtype = "*"; 741 } 742 } 743 744 final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 745 intent.setType((mimeType != null ? mimeType : "*") + '/' + 746 (mimeSubtype != null ? mimeSubtype : "*")); 747 intent.addCategory(Intent.CATEGORY_OPENABLE); 748 intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); 749 if (Build.VERSION.SDK_INT >= 18 && type == FILE_TYPE_MULTIPLE) { 750 intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 751 } 752 if (Build.VERSION.SDK_INT >= 19 && mimeTypes.length > 0) { 753 intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); 754 } 755 756 try { 757 mFileType = type; 758 mFileCallback = callback; 759 activity.startActivityForResult(intent, filePickerRequestCode); 760 } catch (final ActivityNotFoundException e) { 761 Log.e(LOGTAG, "Cannot launch activity", e); 762 callback.dismiss(); 763 } 764 } 765 onFileCallbackResult(final int resultCode, final Intent data)766 public void onFileCallbackResult(final int resultCode, final Intent data) { 767 if (mFileCallback == null) { 768 return; 769 } 770 771 final FileCallback callback = mFileCallback; 772 mFileCallback = null; 773 774 if (resultCode != Activity.RESULT_OK || data == null) { 775 callback.dismiss(); 776 return; 777 } 778 779 final Uri uri = data.getData(); 780 final ClipData clip = data.getClipData(); 781 782 if (mFileType == FILE_TYPE_SINGLE || 783 (mFileType == FILE_TYPE_MULTIPLE && clip == null)) { 784 callback.confirm(mActivity, uri); 785 786 } else if (mFileType == FILE_TYPE_MULTIPLE) { 787 if (clip == null) { 788 Log.w(LOGTAG, "No selected file"); 789 callback.dismiss(); 790 return; 791 } 792 final int count = clip.getItemCount(); 793 final ArrayList<Uri> uris = new ArrayList<>(count); 794 for (int i = 0; i < count; i++) { 795 uris.add(clip.getItemAt(i).getUri()); 796 } 797 callback.confirm(mActivity, uris.toArray(new Uri[uris.size()])); 798 } 799 } 800 onPermissionPrompt(final GeckoSession session, final String title, final GeckoSession.PermissionDelegate.Callback callback)801 public void onPermissionPrompt(final GeckoSession session, final String title, 802 final GeckoSession.PermissionDelegate.Callback callback) { 803 final Activity activity = mActivity; 804 if (activity == null) { 805 callback.reject(); 806 return; 807 } 808 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 809 builder.setTitle(title) 810 .setNegativeButton(android.R.string.cancel, /* onClickListener */ null) 811 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 812 @Override 813 public void onClick(final DialogInterface dialog, final int which) { 814 callback.grant(); 815 } 816 }); 817 818 final AlertDialog dialog = builder.create(); 819 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 820 @Override 821 public void onDismiss(final DialogInterface dialog) { 822 callback.reject(); 823 } 824 }); 825 dialog.show(); 826 } 827 addMediaSpinner(final Context context, final ViewGroup container, final MediaSource[] sources, final String[] sourceNames)828 private Spinner addMediaSpinner(final Context context, final ViewGroup container, 829 final MediaSource[] sources, final String[] sourceNames) { 830 final ArrayAdapter<MediaSource> adapter = new ArrayAdapter<MediaSource>( 831 context, android.R.layout.simple_spinner_item) { 832 private View convertView(final int position, final View view) { 833 if (view != null) { 834 final MediaSource item = getItem(position); 835 ((TextView) view).setText(sourceNames != null ? sourceNames[position] : item.name); 836 } 837 return view; 838 } 839 840 @Override 841 public View getView(final int position, View view, 842 final ViewGroup parent) { 843 return convertView(position, super.getView(position, view, parent)); 844 } 845 846 @Override 847 public View getDropDownView(final int position, final View view, 848 final ViewGroup parent) { 849 return convertView(position, super.getDropDownView(position, view, parent)); 850 } 851 }; 852 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 853 adapter.addAll(sources); 854 855 final Spinner spinner = new Spinner(context); 856 spinner.setAdapter(adapter); 857 spinner.setSelection(0); 858 container.addView(spinner); 859 return spinner; 860 } 861 onMediaPrompt(final GeckoSession session, final String title, final MediaSource[] video, final MediaSource[] audio, final String[] videoNames, final String[] audioNames, final GeckoSession.PermissionDelegate.MediaCallback callback)862 public void onMediaPrompt(final GeckoSession session, final String title, 863 final MediaSource[] video, final MediaSource[] audio, 864 final String[] videoNames, final String[] audioNames, 865 final GeckoSession.PermissionDelegate.MediaCallback callback) { 866 final Activity activity = mActivity; 867 if (activity == null || (video == null && audio == null)) { 868 callback.reject(); 869 return; 870 } 871 final AlertDialog.Builder builder = new AlertDialog.Builder(activity); 872 final LinearLayout container = addStandardLayout(builder, title, /* msg */ null); 873 874 final Spinner videoSpinner; 875 if (video != null) { 876 videoSpinner = addMediaSpinner(builder.getContext(), container, video, videoNames); 877 } else { 878 videoSpinner = null; 879 } 880 881 final Spinner audioSpinner; 882 if (audio != null) { 883 audioSpinner = addMediaSpinner(builder.getContext(), container, audio, audioNames); 884 } else { 885 audioSpinner = null; 886 } 887 888 builder.setNegativeButton(android.R.string.cancel, /* listener */ null) 889 .setPositiveButton(android.R.string.ok, 890 new DialogInterface.OnClickListener() { 891 @Override 892 public void onClick(final DialogInterface dialog, final int which) { 893 final MediaSource video = (videoSpinner != null) 894 ? (MediaSource) videoSpinner.getSelectedItem() : null; 895 final MediaSource audio = (audioSpinner != null) 896 ? (MediaSource) audioSpinner.getSelectedItem() : null; 897 callback.grant(video, audio); 898 } 899 }); 900 901 final AlertDialog dialog = builder.create(); 902 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 903 @Override 904 public void onDismiss(final DialogInterface dialog) { 905 callback.reject(); 906 } 907 }); 908 dialog.show(); 909 } 910 onMediaPrompt(final GeckoSession session, final String title, final MediaSource[] video, final MediaSource[] audio, final GeckoSession.PermissionDelegate.MediaCallback callback)911 public void onMediaPrompt(final GeckoSession session, final String title, 912 final MediaSource[] video, final MediaSource[] audio, 913 final GeckoSession.PermissionDelegate.MediaCallback callback) { 914 onMediaPrompt(session, title, video, audio, null, null, callback); 915 } 916 } 917