1 // Copyright (c) 2014 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.xwalk.core;
7 import java.lang.Runnable;
8 import java.util.ArrayList;
10 import android.app.Activity;
11 import android.app.Dialog;
12 import android.content.BroadcastReceiver;
13 import android.content.Context;
14 import android.content.DialogInterface;
15 import android.content.Intent;
16 import android.content.IntentFilter;
17 import android.content.res.Configuration;
18 import android.graphics.Bitmap;
19 import android.graphics.BitmapFactory;
20 import android.graphics.drawable.BitmapDrawable;
21 import android.graphics.drawable.Drawable;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.graphics.Shader.TileMode;
25 import android.hardware.SensorManager;
26 import android.os.Bundle;
27 import android.util.DisplayMetrics;
28 import android.util.Log;
29 import android.util.TypedValue;
30 import android.view.Display;
31 import android.view.Gravity;
32 import android.view.KeyEvent;
33 import android.view.OrientationEventListener;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.Window;
37 import android.view.WindowManager;
38 import android.widget.FrameLayout;
39 import android.widget.ImageView;
40 import android.widget.ImageView.ScaleType;
41 import android.widget.LinearLayout;
42 import android.widget.RelativeLayout;
44 import org.chromium.content.browser.ContentViewRenderView.FirstRenderedFrameListener;
47 * Provisionally set it as public due to the use of launch screen extension.
50 public class XWalkLaunchScreenManager
51 implements FirstRenderedFrameListener, DialogInterface.OnShowListener,
52 DialogInterface.OnDismissListener, PageLoadListener {
53 // This string will be initialized before extension initialized,
54 // and used by LaunchScreenExtension.
55 private static String mIntentFilterStr;
57 private final static String BORDER_MODE_REPEAT = "repeat";
58 private final static String BORDER_MODE_STRETCH = "stretch";
59 private final static String BORDER_MODE_ROUND = "round";
61 private XWalkView mXWalkView;
62 private Activity mActivity;
63 private Context mLibContext;
64 private Dialog mLaunchScreenDialog;
65 private boolean mPageLoadFinished;
66 private ReadyWhenType mReadyWhen;
67 private boolean mFirstFrameReceived;
68 private BroadcastReceiver mLaunchScreenReadyWhenReceiver;
69 private boolean mCustomHideLaunchScreen;
70 private int mCurrentOrientation;
71 private OrientationEventListener mOrientationListener;
73 private enum ReadyWhenType {
80 private enum BorderModeType {
87 public XWalkLaunchScreenManager(Context context, XWalkView xwView) {
89 mLibContext = context;
90 mActivity = mXWalkView.getActivity();
91 mIntentFilterStr = mActivity.getPackageName() + ".hideLaunchScreen";
94 public void displayLaunchScreen(String readyWhen, final String imageBorderList) {
95 if (mXWalkView == null) return;
96 setReadyWhen(readyWhen);
98 Runnable runnable = new Runnable() {
100 int bgResId = mActivity.getResources().getIdentifier(
101 "launchscreen_bg", "drawable", mActivity.getPackageName());
102 if (bgResId == 0) return;
103 Drawable bgDrawable = mActivity.getResources().getDrawable(bgResId);
104 if (bgDrawable == null) return;
106 mLaunchScreenDialog = new Dialog(mLibContext,
107 android.R.style.Theme_Holo_Light_NoActionBar);
108 if ((mActivity.getWindow().getAttributes().flags &
109 WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0) {
110 mLaunchScreenDialog.getWindow().setFlags(
111 WindowManager.LayoutParams.FLAG_FULLSCREEN,
112 WindowManager.LayoutParams.FLAG_FULLSCREEN);
114 mLaunchScreenDialog.setOnKeyListener(new Dialog.OnKeyListener() {
116 public boolean onKey(DialogInterface arg0, int keyCode,
118 if (keyCode == KeyEvent.KEYCODE_BACK) {
119 performHideLaunchScreen();
120 mActivity.onBackPressed();
125 mLaunchScreenDialog.setOnShowListener(XWalkLaunchScreenManager.this);
126 mLaunchScreenDialog.setOnDismissListener(XWalkLaunchScreenManager.this);
128 mLaunchScreenDialog.getWindow().setBackgroundDrawable(bgDrawable);
129 // Set foreground image
130 RelativeLayout root = getLaunchScreenLayout(imageBorderList);
131 if (root == null) return;
132 mLaunchScreenDialog.setContentView(root);
133 mLaunchScreenDialog.show();
135 // Change the layout depends on the orientation change.
136 mOrientationListener = new OrientationEventListener(mActivity,
137 SensorManager.SENSOR_DELAY_NORMAL) {
138 public void onOrientationChanged(int ori) {
139 if (mLaunchScreenDialog == null || !mLaunchScreenDialog.isShowing()) {
142 int orientation = getScreenOrientation();
143 if (orientation != mCurrentOrientation) {
144 RelativeLayout root = getLaunchScreenLayout(imageBorderList);
145 if (root == null) return;
146 mLaunchScreenDialog.setContentView(root);
150 mOrientationListener.enable();
151 if (mReadyWhen == ReadyWhenType.CUSTOM) registerBroadcastReceiver();
154 mActivity.runOnUiThread(runnable);
158 public void onFirstFrameReceived() {
159 mFirstFrameReceived = true;
160 hideLaunchScreenWhenReady();
164 public void onShow(DialogInterface dialog) {
165 mActivity.getWindow().setBackgroundDrawable(null);
166 if (mFirstFrameReceived) hideLaunchScreenWhenReady();
170 public void onDismiss(DialogInterface dialog) {
171 mOrientationListener.disable();
172 mOrientationListener = null;
176 public void onPageFinished(String url) {
177 mPageLoadFinished = true;
178 hideLaunchScreenWhenReady();
181 public static String getHideLaunchScreenFilterStr() {
182 return mIntentFilterStr;
185 public int getScreenOrientation() {
186 // getResources().getConfiguration().orientation returns wrong value in some devices.
187 // Below is another way to calculate screen orientation.
188 Display display = mActivity.getWindowManager().getDefaultDisplay();
189 Point size = new Point();
190 display.getSize(size);
192 if (size.x < size.y) {
193 orientation = Configuration.ORIENTATION_PORTRAIT;
195 orientation = Configuration.ORIENTATION_LANDSCAPE;
200 private RelativeLayout getLaunchScreenLayout(String imageBorderList) {
201 // Parse the borders depends on orientation.
202 // imageBorderList format:"[default];[landscape];[portrait]"
203 String[] borders = imageBorderList.split(";");
204 // When there is no borders defined, display with no borders.
205 if (borders.length < 1) return parseImageBorder("");
206 int orientation = getScreenOrientation();
207 mCurrentOrientation = orientation;
208 if (borders.length >= 2 && orientation == Configuration.ORIENTATION_LANDSCAPE) {
209 if (borders[1].equals("empty")) {
210 // Has launch_screen.landscape configured, but no image_border set.
211 // Display the iamge with no borders.
212 return parseImageBorder("");
213 } else if (borders[1].isEmpty()) {
214 // No launch_screen.landscape configured.
215 // Use launch_screen.default.
216 return parseImageBorder(borders[0]);
218 return parseImageBorder(borders[1]);
220 } else if (borders.length == 3 && orientation == Configuration.ORIENTATION_PORTRAIT) {
221 if (borders[2].equals("empty")) {
222 // Has launch_screen.portrait configured, but no image_border set.
223 // Display the iamge with no borders.
224 return parseImageBorder("");
225 } else if (borders[2].isEmpty()) {
226 // No launch_screen.portrait configured.
227 // Use launch_screen.default.
228 return parseImageBorder(borders[0]);
230 return parseImageBorder(borders[2]);
234 return parseImageBorder(borders[0]);
237 private int getSuitableSize(int maxSize, int divider) {
238 int finalSize = divider;
239 float minMod = divider;
240 for (; divider > 1; divider--) {
241 int mod = maxSize % divider;
242 // Found the suitable size.
247 // Record the best suitable one.
248 // If there is no mod==0 found, return the divider which min(mod).
258 * Get each section from 9-piece format image depends on the spec defined
259 * @param img The foreground image.
260 * @param x The position where the section start.
261 * @param y The position where the section start.
262 * @param width The width of the section.
263 * @param height The height of the section.
264 * @param mode The border type for this section.
265 * @param maxWidth When mode == ROUND, this will be used.
266 * @param maxHeight When mode == ROUND, this will be used.
267 * @return The ImageView for this section.
269 private ImageView getSubImageView(Bitmap img, int x, int y, int width, int height,
270 BorderModeType mode, int maxWidth, int maxHeight) {
271 if (img == null) return null;
273 if (width <= 0 || height <= 0) return null;
275 // Check whether the section is inside the foreground image.
276 Rect imgRect = new Rect(0, 0, img.getWidth(), img.getHeight());
277 Rect subRect = new Rect(x, y, x + width, y + height);
278 if (!imgRect.contains(subRect)) return null;
280 Bitmap subImage = Bitmap.createBitmap(img, x, y, width, height);
281 ImageView subImageView = new ImageView(mActivity);
282 BitmapDrawable drawable;
283 if (mode == BorderModeType.ROUND) {
284 int originW = subImage.getWidth();
285 int originH = subImage.getHeight();
288 // Scale down the sub image to let the last image not cropped when it's repeated.
289 if (maxWidth > 0) newW = getSuitableSize(maxWidth, originW);
290 if (maxHeight > 0) newH = getSuitableSize(maxHeight, originH);
291 // recreate the new scaled bitmap.
292 Bitmap resizedBitmap = Bitmap.createScaledBitmap(subImage, newW, newH, true);
293 // Treat as repeat mode.
294 subImage = resizedBitmap;
295 mode = BorderModeType.REPEAT;
297 if (mode == BorderModeType.REPEAT) {
298 drawable = new BitmapDrawable(mActivity.getResources(), subImage);
299 drawable.setTileModeXY(TileMode.REPEAT, TileMode.REPEAT);
300 subImageView.setImageDrawable(drawable);
301 subImageView.setScaleType(ScaleType.FIT_XY);
302 } else if (mode == BorderModeType.STRETCH) {
303 subImageView.setImageBitmap(subImage);
304 subImageView.setScaleType(ScaleType.FIT_XY);
306 subImageView.setImageBitmap(subImage);
312 private int getStatusBarHeight() {
313 int resourceId = mActivity.getResources().getIdentifier(
314 "status_bar_height", "dimen", "android");
315 if (resourceId > 0) {
316 return mActivity.getResources().getDimensionPixelSize(resourceId);
318 // If not found, return default one.
322 private RelativeLayout parseImageBorder(String imageBorder) {
326 int bottomBorder = 0;
327 BorderModeType horizontalMode = BorderModeType.STRETCH;
328 BorderModeType verticalMode = BorderModeType.STRETCH;
330 if (imageBorder.equals("empty")) imageBorder = "";
332 // Parse the value of image_border.
333 String[] items = imageBorder.split(" ");
334 ArrayList<String> borders = new ArrayList<String>();
335 ArrayList<BorderModeType> modes = new ArrayList<BorderModeType>();
336 for (int i = 0; i < items.length; i++) {
337 String item = items[i];
338 if (item.endsWith("px")) {
339 borders.add(item.replaceAll("px", ""));
340 } else if (item.equals(BORDER_MODE_REPEAT)) {
341 modes.add(BorderModeType.REPEAT);
342 } else if (item.equals(BORDER_MODE_STRETCH)) {
343 modes.add(BorderModeType.STRETCH);
344 } else if (item.equals(BORDER_MODE_ROUND)) {
345 modes.add(BorderModeType.ROUND);
348 // Parse borders as defined by the spec.
350 if (borders.size() == 1) {
351 topBorder = rightBorder = leftBorder = bottomBorder =
352 Integer.valueOf(borders.get(0));
353 } else if (borders.size() == 2) {
354 topBorder = bottomBorder = Integer.valueOf(borders.get(0));
355 rightBorder = leftBorder = Integer.valueOf(borders.get(1));
356 } else if (borders.size() == 3) {
357 rightBorder = leftBorder = Integer.valueOf(borders.get(1));
358 topBorder = Integer.valueOf(borders.get(0));
359 bottomBorder = Integer.valueOf(borders.get(2));
360 } else if (borders.size() == 4) {
361 topBorder = Integer.valueOf(borders.get(0));
362 rightBorder = Integer.valueOf(borders.get(1));
363 leftBorder = Integer.valueOf(borders.get(2));
364 bottomBorder = Integer.valueOf(borders.get(3));
366 } catch (NumberFormatException e) {
367 topBorder = rightBorder = leftBorder = bottomBorder = 0;
370 // The border values are dpi from manifest.json, need to translate to px.
371 DisplayMetrics matrix = mActivity.getResources().getDisplayMetrics();
372 topBorder = (int)TypedValue.applyDimension(
373 TypedValue.COMPLEX_UNIT_DIP, topBorder, matrix);
374 rightBorder = (int)TypedValue.applyDimension(
375 TypedValue.COMPLEX_UNIT_DIP, rightBorder, matrix);
376 leftBorder = (int)TypedValue.applyDimension(
377 TypedValue.COMPLEX_UNIT_DIP, leftBorder, matrix);
378 bottomBorder = (int)TypedValue.applyDimension(
379 TypedValue.COMPLEX_UNIT_DIP, bottomBorder, matrix);
381 // Parse border mode as spec defined.
382 if (modes.size() == 1) {
383 horizontalMode = verticalMode = modes.get(0);
384 } else if (modes.size() == 2) {
385 horizontalMode = modes.get(0);
386 verticalMode = modes.get(1);
389 // Get foreground image
390 int imgResId = mActivity.getResources().getIdentifier(
391 "launchscreen_img", "drawable", mActivity.getPackageName());
392 if (imgResId == 0) return null;
393 Bitmap img = BitmapFactory.decodeResource(mActivity.getResources(), imgResId);
394 if (img == null) return null;
396 // Create the 9-piece layout as spec defined.
397 RelativeLayout root = new RelativeLayout(mActivity);
398 root.setLayoutParams(new RelativeLayout.LayoutParams(
399 ViewGroup.LayoutParams.MATCH_PARENT,
400 ViewGroup.LayoutParams.MATCH_PARENT));
401 RelativeLayout.LayoutParams params;
402 ImageView subImageView;
404 // Get Screen width and height.
405 Display display = mActivity.getWindowManager().getDefaultDisplay();
406 Point size = new Point();
407 display.getSize(size);
409 // For non fullscreen, the height should substract status bar height
410 if ((mActivity.getWindow().getAttributes().flags &
411 WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0) {
412 size.y -= getStatusBarHeight();
415 // Image section-1 top left
416 subImageView = getSubImageView(img, 0, 0, leftBorder, topBorder, BorderModeType.NONE, 0, 0);
417 if (subImageView != null) {
418 params = new RelativeLayout.LayoutParams(
419 RelativeLayout.LayoutParams.WRAP_CONTENT,
420 RelativeLayout.LayoutParams.WRAP_CONTENT);
421 params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
422 params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
423 root.addView(subImageView, params);
426 // Image section-2 top
427 subImageView = getSubImageView(img, leftBorder, 0, img.getWidth() - leftBorder
428 - rightBorder, topBorder, horizontalMode, size.x - leftBorder - rightBorder, 0);
429 if (subImageView != null) {
430 params = new RelativeLayout.LayoutParams(
431 RelativeLayout.LayoutParams.MATCH_PARENT,
432 RelativeLayout.LayoutParams.WRAP_CONTENT);
433 params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
434 params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
435 params.leftMargin = leftBorder;
436 params.rightMargin = rightBorder;
437 root.addView(subImageView, params);
440 // Image section-3 top right
441 subImageView = getSubImageView(img, img.getWidth() - rightBorder, 0,
442 rightBorder, topBorder, BorderModeType.NONE, 0, 0);
443 if (subImageView != null) {
444 params = new RelativeLayout.LayoutParams(
445 RelativeLayout.LayoutParams.WRAP_CONTENT,
446 RelativeLayout.LayoutParams.WRAP_CONTENT);
447 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
448 params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
449 root.addView(subImageView, params);
452 // Image section-4 left
453 subImageView = getSubImageView(img, 0, topBorder, leftBorder, img.getHeight()
454 - topBorder - bottomBorder, verticalMode, 0, size.y - topBorder - bottomBorder);
455 if (subImageView != null) {
456 params = new RelativeLayout.LayoutParams(
457 RelativeLayout.LayoutParams.WRAP_CONTENT,
458 RelativeLayout.LayoutParams.MATCH_PARENT);
459 params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
460 params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
461 params.topMargin = topBorder;
462 params.bottomMargin = bottomBorder;
463 root.addView(subImageView, params);
466 // Image section-5 middle
467 subImageView = getSubImageView(img, leftBorder, topBorder, img.getWidth() - leftBorder - rightBorder,
468 img.getHeight() - topBorder - bottomBorder, BorderModeType.NONE, 0, 0);
469 if (subImageView != null) {
470 subImageView.setScaleType(ScaleType.FIT_XY);
471 params = new RelativeLayout.LayoutParams(
472 RelativeLayout.LayoutParams.MATCH_PARENT,
473 RelativeLayout.LayoutParams.MATCH_PARENT);
474 params.leftMargin = leftBorder;
475 params.topMargin = topBorder;
476 params.rightMargin = rightBorder;
477 params.bottomMargin = bottomBorder;
478 root.addView(subImageView, params);
481 // Image section-6 right
482 subImageView = getSubImageView(img, img.getWidth() - rightBorder, topBorder, rightBorder,
483 img.getHeight() - topBorder - bottomBorder, verticalMode, 0,
484 size.y - topBorder - bottomBorder);
485 if (subImageView != null) {
486 params = new RelativeLayout.LayoutParams(
487 RelativeLayout.LayoutParams.WRAP_CONTENT,
488 RelativeLayout.LayoutParams.MATCH_PARENT);
489 params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
490 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
491 params.topMargin = topBorder;
492 params.bottomMargin = bottomBorder;
493 root.addView(subImageView, params);
496 // Image section-7 bottom left
497 subImageView = getSubImageView(img, 0, img.getHeight() - bottomBorder,
498 leftBorder, bottomBorder, BorderModeType.NONE, 0, 0);
499 if (subImageView != null) {
500 params = new RelativeLayout.LayoutParams(
501 RelativeLayout.LayoutParams.WRAP_CONTENT,
502 RelativeLayout.LayoutParams.WRAP_CONTENT);
503 params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
504 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
505 root.addView(subImageView, params);
508 // Image section-8 bottom
509 subImageView = getSubImageView(img, leftBorder, img.getHeight() - bottomBorder,
510 img.getWidth() - leftBorder - rightBorder, bottomBorder, horizontalMode,
511 size.x - leftBorder - rightBorder, 0);
512 if (subImageView != null) {
513 params = new RelativeLayout.LayoutParams(
514 RelativeLayout.LayoutParams.MATCH_PARENT,
515 RelativeLayout.LayoutParams.WRAP_CONTENT);
516 params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
517 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
518 params.leftMargin = leftBorder;
519 params.rightMargin = rightBorder;
520 root.addView(subImageView, params);
523 // Image section-9 bottom right
524 subImageView = getSubImageView(img, img.getWidth() - rightBorder,
525 img.getHeight() - bottomBorder, rightBorder, bottomBorder,
526 BorderModeType.NONE, 0, 0);
527 if (subImageView != null) {
528 params = new RelativeLayout.LayoutParams(
529 RelativeLayout.LayoutParams.WRAP_CONTENT,
530 RelativeLayout.LayoutParams.WRAP_CONTENT);
531 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
532 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
533 root.addView(subImageView, params);
538 private void registerBroadcastReceiver() {
539 IntentFilter intentFilter = new IntentFilter(mIntentFilterStr);
540 mLaunchScreenReadyWhenReceiver = new BroadcastReceiver() {
542 public void onReceive(Context context, Intent intent) {
543 mCustomHideLaunchScreen = true;
544 hideLaunchScreenWhenReady();
547 mActivity.registerReceiver(mLaunchScreenReadyWhenReceiver, intentFilter);
550 private void hideLaunchScreenWhenReady() {
551 if (mLaunchScreenDialog == null || !mFirstFrameReceived) return;
552 if (mReadyWhen == ReadyWhenType.FIRST_PAINT) {
553 performHideLaunchScreen();
554 } else if (mReadyWhen == ReadyWhenType.USER_INTERACTIVE) {
555 // TODO: Need to listen js DOMContentLoaded event,
556 // will be implemented in the next step.
557 performHideLaunchScreen();
558 } else if (mReadyWhen == ReadyWhenType.COMPLETE) {
559 if (mPageLoadFinished) performHideLaunchScreen();
560 } else if (mReadyWhen == ReadyWhenType.CUSTOM) {
561 if (mCustomHideLaunchScreen) performHideLaunchScreen();
565 private void performHideLaunchScreen() {
566 mLaunchScreenDialog.dismiss();
567 mLaunchScreenDialog = null;
568 if (mReadyWhen == ReadyWhenType.CUSTOM) {
569 mActivity.unregisterReceiver(mLaunchScreenReadyWhenReceiver);
573 private void setReadyWhen(String readyWhen) {
574 if (readyWhen.equals("first-paint")) {
575 mReadyWhen = ReadyWhenType.FIRST_PAINT;
576 } else if (readyWhen.equals("user-interactive")) {
577 mReadyWhen = ReadyWhenType.USER_INTERACTIVE;
578 } else if (readyWhen.equals("complete")) {
579 mReadyWhen = ReadyWhenType.COMPLETE;
580 } else if (readyWhen.equals("custom")) {
581 mReadyWhen = ReadyWhenType.CUSTOM;
583 mReadyWhen = ReadyWhenType.FIRST_PAINT;