From 35614fdc7d13179ba9cbc80f15eca6d63cf3229d Mon Sep 17 00:00:00 2001 From: Vineet Patil Date: Tue, 17 Nov 2015 14:23:05 -0800 Subject: Implementation of folder animation changes as per cm-13.0 Folder animations in parity with cm. Folder Layout Revision Updated the folder layout to reflect design revisions based on community feedback Change-Id: Ica1bb796dfccf779a954cf0903a1ae94a010ea1a Conflicts: res/drawable-hdpi/folder_bg.9.png res/drawable-hdpi/folder_fill_highlight.9.png res/drawable-mdpi/folder_bg.9.png res/drawable-mdpi/folder_fill_highlight.9.png res/drawable-xhdpi/folder_bg.9.png res/drawable-xhdpi/folder_fill_highlight.9.png res/drawable-xxhdpi/folder_bg.9.png res/drawable-xxhdpi/folder_fill_highlight.9.png res/layout-land/launcher.xml res/layout-port/launcher.xml res/layout-sw720dp/launcher.xml res/layout/user_folder.xml res/values/dimens.xml src/com/android/launcher3/Folder.java src/com/android/launcher3/FolderIcon.java src/com/android/launcher3/Launcher.java Folder animation changes as per cm-13.0 Change-Id: I019511a58bd9f6a60a3c9b68c826726881cec83f Implementing folder animations Change-Id: I83f17c996ecc894ce22fd195b9b33caf58e2e822 --- res/anim/drop_down.xml | 13 ++ res/anim/enter_from_left.xml | 12 ++ res/anim/enter_from_right.xml | 12 ++ res/anim/exit_out_left.xml | 13 ++ res/anim/exit_out_right.xml | 12 ++ res/anim/fade_in_fast.xml | 23 ++ res/anim/fade_out_fast.xml | 23 ++ res/drawable-hdpi/folder_bg.9.png | Bin 0 -> 823 bytes res/drawable-hdpi/folder_bg_opaque.9.png | Bin 0 -> 199 bytes res/drawable-hdpi/folder_fill_highlight.9.png | Bin 0 -> 14914 bytes res/drawable-mdpi/folder_bg.9.png | Bin 0 -> 510 bytes res/drawable-mdpi/folder_bg_opaque.9.png | Bin 0 -> 160 bytes res/drawable-mdpi/folder_fill_highlight.9.png | Bin 0 -> 14764 bytes res/drawable-xhdpi/folder_bg.9.png | Bin 0 -> 1023 bytes res/drawable-xhdpi/folder_bg_opaque.9.png | Bin 0 -> 249 bytes res/drawable-xhdpi/folder_fill_highlight.9.png | Bin 0 -> 15075 bytes res/drawable-xxhdpi/folder_bg.9.png | Bin 0 -> 1678 bytes res/drawable-xxhdpi/folder_bg_opaque.9.png | Bin 0 -> 312 bytes res/drawable-xxhdpi/folder_fill_highlight.9.png | Bin 0 -> 15836 bytes res/drawable/folder_container.xml | 25 +++ res/drawable/folder_locked.xml | 14 ++ res/drawable/folder_unlocked.xml | 13 ++ res/layout-land/launcher.xml | 19 +- res/layout-port/launcher.xml | 21 +- res/layout-sw720dp/launcher.xml | 19 +- res/layout/folder_icon.xml | 50 ++++- res/layout/user_folder.xml | 6 +- res/values/attrs.xml | 14 +- res/values/dimens.xml | 11 + res/values/strings.xml | 5 +- res/values/styles.xml | 2 +- src/com/android/launcher3/CellLayout.java | 16 +- src/com/android/launcher3/Folder.java | 231 ++++++++++++++++++--- src/com/android/launcher3/FolderIcon.java | 142 ++++++++++--- src/com/android/launcher3/FolderInfo.java | 1 - .../android/launcher3/InsettableFrameLayout.java | 14 +- src/com/android/launcher3/Launcher.java | 69 +++++- src/com/android/launcher3/LauncherSettings.java | 2 +- 38 files changed, 688 insertions(+), 94 deletions(-) create mode 100644 res/anim/drop_down.xml create mode 100644 res/anim/enter_from_left.xml create mode 100644 res/anim/enter_from_right.xml create mode 100644 res/anim/exit_out_left.xml create mode 100644 res/anim/exit_out_right.xml create mode 100644 res/anim/fade_in_fast.xml create mode 100644 res/anim/fade_out_fast.xml create mode 100644 res/drawable-hdpi/folder_bg.9.png create mode 100755 res/drawable-hdpi/folder_bg_opaque.9.png create mode 100644 res/drawable-hdpi/folder_fill_highlight.9.png create mode 100644 res/drawable-mdpi/folder_bg.9.png create mode 100755 res/drawable-mdpi/folder_bg_opaque.9.png create mode 100644 res/drawable-mdpi/folder_fill_highlight.9.png create mode 100644 res/drawable-xhdpi/folder_bg.9.png create mode 100755 res/drawable-xhdpi/folder_bg_opaque.9.png create mode 100644 res/drawable-xhdpi/folder_fill_highlight.9.png create mode 100644 res/drawable-xxhdpi/folder_bg.9.png create mode 100755 res/drawable-xxhdpi/folder_bg_opaque.9.png create mode 100644 res/drawable-xxhdpi/folder_fill_highlight.9.png create mode 100644 res/drawable/folder_container.xml create mode 100644 res/drawable/folder_locked.xml create mode 100644 res/drawable/folder_unlocked.xml diff --git a/res/anim/drop_down.xml b/res/anim/drop_down.xml new file mode 100644 index 000000000..49059a048 --- /dev/null +++ b/res/anim/drop_down.xml @@ -0,0 +1,13 @@ + + + + diff --git a/res/anim/enter_from_left.xml b/res/anim/enter_from_left.xml new file mode 100644 index 000000000..e2bdbdda3 --- /dev/null +++ b/res/anim/enter_from_left.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/res/anim/enter_from_right.xml b/res/anim/enter_from_right.xml new file mode 100644 index 000000000..02a56c7ae --- /dev/null +++ b/res/anim/enter_from_right.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/res/anim/exit_out_left.xml b/res/anim/exit_out_left.xml new file mode 100644 index 000000000..eae925a2a --- /dev/null +++ b/res/anim/exit_out_left.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/res/anim/exit_out_right.xml b/res/anim/exit_out_right.xml new file mode 100644 index 000000000..7345c942d --- /dev/null +++ b/res/anim/exit_out_right.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml new file mode 100644 index 000000000..4fa9847aa --- /dev/null +++ b/res/anim/fade_in_fast.xml @@ -0,0 +1,23 @@ + + + + diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml new file mode 100644 index 000000000..a061a6ca9 --- /dev/null +++ b/res/anim/fade_out_fast.xml @@ -0,0 +1,23 @@ + + + + diff --git a/res/drawable-hdpi/folder_bg.9.png b/res/drawable-hdpi/folder_bg.9.png new file mode 100644 index 000000000..ee0090c09 Binary files /dev/null and b/res/drawable-hdpi/folder_bg.9.png differ diff --git a/res/drawable-hdpi/folder_bg_opaque.9.png b/res/drawable-hdpi/folder_bg_opaque.9.png new file mode 100755 index 000000000..08e152e49 Binary files /dev/null and b/res/drawable-hdpi/folder_bg_opaque.9.png differ diff --git a/res/drawable-hdpi/folder_fill_highlight.9.png b/res/drawable-hdpi/folder_fill_highlight.9.png new file mode 100644 index 000000000..b82302ba6 Binary files /dev/null and b/res/drawable-hdpi/folder_fill_highlight.9.png differ diff --git a/res/drawable-mdpi/folder_bg.9.png b/res/drawable-mdpi/folder_bg.9.png new file mode 100644 index 000000000..4039da560 Binary files /dev/null and b/res/drawable-mdpi/folder_bg.9.png differ diff --git a/res/drawable-mdpi/folder_bg_opaque.9.png b/res/drawable-mdpi/folder_bg_opaque.9.png new file mode 100755 index 000000000..673d740ae Binary files /dev/null and b/res/drawable-mdpi/folder_bg_opaque.9.png differ diff --git a/res/drawable-mdpi/folder_fill_highlight.9.png b/res/drawable-mdpi/folder_fill_highlight.9.png new file mode 100644 index 000000000..7c6a0d456 Binary files /dev/null and b/res/drawable-mdpi/folder_fill_highlight.9.png differ diff --git a/res/drawable-xhdpi/folder_bg.9.png b/res/drawable-xhdpi/folder_bg.9.png new file mode 100644 index 000000000..1fbe1d80f Binary files /dev/null and b/res/drawable-xhdpi/folder_bg.9.png differ diff --git a/res/drawable-xhdpi/folder_bg_opaque.9.png b/res/drawable-xhdpi/folder_bg_opaque.9.png new file mode 100755 index 000000000..42a1e1d5d Binary files /dev/null and b/res/drawable-xhdpi/folder_bg_opaque.9.png differ diff --git a/res/drawable-xhdpi/folder_fill_highlight.9.png b/res/drawable-xhdpi/folder_fill_highlight.9.png new file mode 100644 index 000000000..f5f0bd08d Binary files /dev/null and b/res/drawable-xhdpi/folder_fill_highlight.9.png differ diff --git a/res/drawable-xxhdpi/folder_bg.9.png b/res/drawable-xxhdpi/folder_bg.9.png new file mode 100644 index 000000000..3b2bc4253 Binary files /dev/null and b/res/drawable-xxhdpi/folder_bg.9.png differ diff --git a/res/drawable-xxhdpi/folder_bg_opaque.9.png b/res/drawable-xxhdpi/folder_bg_opaque.9.png new file mode 100755 index 000000000..25a4ffffb Binary files /dev/null and b/res/drawable-xxhdpi/folder_bg_opaque.9.png differ diff --git a/res/drawable-xxhdpi/folder_fill_highlight.9.png b/res/drawable-xxhdpi/folder_fill_highlight.9.png new file mode 100644 index 000000000..4dc29f46c Binary files /dev/null and b/res/drawable-xxhdpi/folder_fill_highlight.9.png differ diff --git a/res/drawable/folder_container.xml b/res/drawable/folder_container.xml new file mode 100644 index 000000000..b0a1c8492 --- /dev/null +++ b/res/drawable/folder_container.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/res/drawable/folder_locked.xml b/res/drawable/folder_locked.xml new file mode 100644 index 000000000..8b887896d --- /dev/null +++ b/res/drawable/folder_locked.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/res/drawable/folder_unlocked.xml b/res/drawable/folder_unlocked.xml new file mode 100644 index 000000000..d34d9b764 --- /dev/null +++ b/res/drawable/folder_unlocked.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml index 6500ebcd2..3a93365a7 100644 --- a/res/layout-land/launcher.xml +++ b/res/layout-land/launcher.xml @@ -21,7 +21,24 @@ android:id="@+id/launcher" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:fitsSystemWindows="true" + android:background="@drawable/workspace_bg"> + + + + + + android:fitsSystemWindows="true" + android:background="@drawable/workspace_bg"> + + + + + + android:fitsSystemWindows="true" + android:background="@drawable/workspace_bg"> + + + + + - + android:layout_width="@dimen/folder_icon" + android:layout_height="@dimen/folder_icon" + android:layout_gravity="center_horizontal|top" + android:background="@drawable/folder_bg" > + + + + + + + diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml index 252ebf01e..2152a9986 100644 --- a/res/layout/user_folder.xml +++ b/res/layout/user_folder.xml @@ -18,7 +18,7 @@ xmlns:launcher="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@drawable/quantum_panel" + android:background="@drawable/folder_bg" android:elevation="5dp" android:orientation="vertical" > @@ -67,7 +67,7 @@ android:paddingBottom="8dp" android:paddingTop="4dp" android:singleLine="true" - android:textColor="#ff777777" + android:textColor="@color/workspace_icon_text_color" android:textColorHighlight="#ffCCCCCC" android:textColorHint="#ff808080" android:textSize="14sp" /> @@ -81,4 +81,4 @@ - \ No newline at end of file + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 827332ad7..7ffebce9b 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -113,7 +113,19 @@ + + + + - + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 36721797e..e3c81941c 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -121,6 +121,13 @@ 4dp + 6dp + 10dp + 48dp + + + 64dp + 22dp 24dp @@ -137,4 +144,8 @@ 8dp 2dp + + 300 + 100 + diff --git a/res/values/strings.xml b/res/values/strings.xml index fefadef28..b54478860 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -31,7 +31,7 @@ com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST - Launcher3 + Launcher3 @@ -103,6 +103,9 @@ Allows the app to change the settings and shortcuts in Home. + + %1$s is not allowed to make phone calls + diff --git a/res/values/styles.xml b/res/values/styles.xml index 7d60cbe0a..9104bc92d 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -49,7 +49,7 @@ diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 84e2d49c2..a99d791bd 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -504,13 +504,15 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // Draw inner ring d = FolderRingAnimator.sSharedInnerRingDrawable; - width = (int) (fra.getInnerRingSize() * getChildrenScale()); - height = width; - canvas.save(); - canvas.translate(centerX - width / 2, centerY - width / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); + if (d != null) { + width = (int) (fra.getInnerRingSize() * getChildrenScale()); + height = width; + canvas.save(); + canvas.translate(centerX - width / 2, centerY - width / 2); + d.setBounds(0, 0, width, height); + d.draw(canvas); + canvas.restore(); + } } } diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index c1aa35669..994d7d30d 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -25,11 +25,15 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; import android.os.Build; import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; import android.text.InputType; import android.text.Selection; import android.text.Spannable; @@ -46,8 +50,10 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -89,6 +95,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList */ public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY; + private static final int CLOSE_FOLDER_DELAY_MS = 150; + + private static final int ALPHA_DELAY_MULT = 15; + /** * Fraction of icon width which behave as scroll region. */ @@ -96,6 +106,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private static final int FOLDER_NAME_ANIMATION_DURATION = 633; + private static final int REORDER_ANIMATION_DURATION = 230; private static final int REORDER_DELAY = 250; private static final int ON_EXIT_CLOSE_DELAY = 400; private static final Rect sTempRect = new Rect(); @@ -116,6 +127,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private final InputMethodManager mInputMethodManager; + private final PowerManager mPowerManager; + protected final Launcher mLauncher; protected DragController mDragController; protected FolderInfo mInfo; @@ -171,6 +184,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mInputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + Resources res = getResources(); mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration); mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); @@ -440,9 +455,41 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList setScaleY(1f); setAlpha(1f); mState = STATE_SMALL; + + View reveal = mLauncher.findViewById(R.id.reveal_fake_page_container); + reveal.setVisibility(View.VISIBLE); + View revealFolderIcon = mLauncher.findViewById(R.id.reveal_fake_folder_icon); + revealFolderIcon.setVisibility(View.INVISIBLE); + } + + private void prepareFakeFolderIcon() { + mFolderIcon.buildDrawingCache(true); + + Bitmap fakeFolderIcon = Bitmap.createBitmap(mFolderIcon.getDrawingCache()); + View fakeFolderIconView = mLauncher.findViewById(R.id.reveal_fake_folder_icon); + FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) + fakeFolderIconView.getLayoutParams(); + + // Get globalVisibleRect of the folderIcon. getWidth and getHeight are inaccurate for + // hotseat icons + Rect rect = new Rect(); + mFolderIcon.getGlobalVisibleRect(rect); + + flp.height = rect.height(); + flp.width = rect.width(); + + fakeFolderIconView.setLayoutParams(flp); + + int [] folderIconXY = new int[2]; + mFolderIcon.getLocationOnScreen(folderIconXY); + fakeFolderIconView.setX(folderIconXY[0]); + fakeFolderIconView.setY(folderIconXY[1]); + + fakeFolderIconView.setBackground(new BitmapDrawable(null, fakeFolderIcon)); + fakeFolderIconView.setVisibility(View.INVISIBLE); } - public void animateOpen() { + public void animateOpen(Workspace workspace, int[] folderTouch) { if (!(getParent() instanceof DragLayer)) return; mContent.completePendingPageChanges(); @@ -473,7 +520,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } }; } else { - prepareReveal(); centerAboutIcon(); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); @@ -490,7 +536,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); drift.setDuration(mMaterialExpandDuration); drift.setStartDelay(mMaterialExpandStagger); - drift.setInterpolator(new LogDecelerateInterpolator(100, 0)); + drift.setInterpolator(new LogDecelerateInterpolator(60, 0)); int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); @@ -513,6 +559,34 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList textAlpha.setStartDelay(mMaterialExpandStagger); textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + prepareFakeFolderIcon(); + float iconTransY = getResources().getInteger(R.integer.folder_icon_translate_y_dist); + + final View fakeFolderIconView = mLauncher.findViewById(R.id.reveal_fake_folder_icon); + float baseIconTranslationY = fakeFolderIconView.getTranslationY(); + PropertyValuesHolder iconty = PropertyValuesHolder.ofFloat("translationY", + baseIconTranslationY, baseIconTranslationY + iconTransY); + PropertyValuesHolder iconAlpha = PropertyValuesHolder.ofFloat("alpha", 1f, 0f); + + Animator fakeFolderIcon = LauncherAnimUtils.ofPropertyValuesHolder(fakeFolderIconView, + iconty, iconAlpha); + fakeFolderIcon.setDuration(mMaterialExpandDuration); + fakeFolderIcon.setInterpolator(new AccelerateInterpolator(1.5f)); + fakeFolderIcon.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mFolderIcon.setAlpha(0); + fakeFolderIconView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + fakeFolderIconView.setVisibility(View.INVISIBLE); + } + }); + + prepareReveal(); + anim.play(drift); anim.play(iconsAlpha); anim.play(textAlpha); @@ -526,7 +600,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Override public void run() { mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); - mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); } }; } @@ -535,6 +608,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void onAnimationStart(Animator animation) { sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, mContent.getAccessibilityDescription()); + hideWorkspace(); mState = STATE_ANIMATING; } @Override @@ -628,31 +702,120 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } } - public void animateClosed() { + public int getState() { + return mState; + } + + public void animateClosed(final boolean animate) { if (!(getParent() instanceof DragLayer)) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); - final ObjectAnimator oa = - LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); + AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onCloseComplete(); - setLayerType(LAYER_TYPE_NONE, null); - mState = STATE_SMALL; - } + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); + float transY = getResources().getInteger(R.integer.folder_translate_y_dist); + PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", 0f, + transY); + + setLayerType(LAYER_TYPE_HARDWARE, null); + + float animatorDurationScale = Settings.Global.getFloat(getContext().getContentResolver(), + Settings.Global.ANIMATOR_DURATION_SCALE, 1); + ObjectAnimator oa; + if (mPowerManager.isPowerSaveMode() || animatorDurationScale < 0.01f) { + // power save mode is no fun - skip alpha animation and just set it to 0 + // otherwise the icons will stay around until the duration of the animation + oa = LauncherAnimUtils.ofPropertyValuesHolder(this, translationY); + setAlpha(0f); + } else { + oa = LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, translationY); + } + + oa.setDuration(mMaterialExpandDuration); + oa.setInterpolator(new LogDecelerateInterpolator(60, 0)); + anim.play(oa); + + Animator reverseRevealAnim = null; + Animator fakeFolderIconAnim = null; + + if (animate) { + + prepareFakeFolderIcon(); + float iconTransY = getResources().getInteger(R.integer.folder_icon_translate_y_dist); + + final View fakeFolderIconView = mLauncher.findViewById(R.id.reveal_fake_folder_icon); + float baseIconTranslationY = fakeFolderIconView.getTranslationY(); + PropertyValuesHolder iconty = PropertyValuesHolder.ofFloat("translationY", + baseIconTranslationY + iconTransY, baseIconTranslationY); + PropertyValuesHolder iconAlpha = PropertyValuesHolder.ofFloat("alpha", 0f, 1f); + + fakeFolderIconAnim = LauncherAnimUtils.ofPropertyValuesHolder(fakeFolderIconView, + iconty, iconAlpha); + fakeFolderIconAnim.setDuration(mMaterialExpandDuration); + fakeFolderIconAnim.setInterpolator(new DecelerateInterpolator(2f)); + fakeFolderIconAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mFolderIcon.setAlpha(0); + fakeFolderIconView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + fakeFolderIconView.setVisibility(View.INVISIBLE); + mFolderIcon.setAlpha(1); + + View revealView = mLauncher.findViewById(R.id.reveal_fake_page_container); + revealView.setVisibility(View.INVISIBLE); + } + }); + } else { + View revealView = mLauncher.findViewById(R.id.reveal_fake_page_container); + revealView.setVisibility(View.INVISIBLE); + mFolderIcon.setAlpha(1); + } + + if (reverseRevealAnim != null) { + anim.play(reverseRevealAnim); + } + if (fakeFolderIconAnim != null) { + anim.play(fakeFolderIconAnim); + } + + anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_closed)); + unHideWorkspace(); mState = STATE_ANIMATING; } + + @Override + public void onAnimationEnd(Animator animation) { + onCloseComplete(); + setLayerType(LAYER_TYPE_NONE, null); + mState = STATE_SMALL; + } }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - oa.start(); + + anim.start(); + } + + private int mSavedWidgetsVisibilityState = INVISIBLE; + private void hideWorkspace() { + mSavedWidgetsVisibilityState = mLauncher.getWidgetsView().getVisibility(); + mLauncher.getWidgetsView().setVisibility(INVISIBLE); + mLauncher.getWorkspace().setVisibility(INVISIBLE); + mLauncher.getHotseat().setVisibility(INVISIBLE); + mLauncher.getSearchDropTargetBar().setVisibility(INVISIBLE); + mLauncher.getPageIndicator().setVisibility(INVISIBLE); + } + + private void unHideWorkspace() { + mLauncher.getWidgetsView().setVisibility(mSavedWidgetsVisibilityState); + mLauncher.getWorkspace().setVisibility(VISIBLE); + mLauncher.getHotseat().setVisibility(VISIBLE); + mLauncher.getSearchDropTargetBar().setVisibility(VISIBLE); + mLauncher.getPageIndicator().setVisibility(VISIBLE); } public boolean acceptDrop(DragObject d) { @@ -966,22 +1129,22 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2); int centeredLeft = centerX - width / 2; int centeredTop = centerY - height / 2; + int currentPage = mLauncher.getWorkspace().getNextPage(); + + // We first fetch the currently visible CellLayoutChildren + CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage); + ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets(); + Rect bounds = new Rect(); + parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); - // We need to bound the folder to the currently visible workspace area - mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect); - int left = Math.min(Math.max(sTempRect.left, centeredLeft), - sTempRect.left + sTempRect.width() - width); - int top = Math.min(Math.max(sTempRect.top, centeredTop), - sTempRect.top + sTempRect.height() - height); - if (grid.isPhone && (grid.availableWidthPx - width) < grid.iconSizePx) { - // Center the folder if it is full (on phones only) - left = (grid.availableWidthPx - width) / 2; - } else if (width >= sTempRect.width()) { + // Center the folder + int left = (grid.availableWidthPx - width) / 2; + // Drop the top down a little so it isn't bounded by the page indicators + int top = (int) (bounds.top + (bounds.height() * 1.15) - height); + + if (width >= bounds.width()) { // If the folder doesn't fit within the bounds, center it about the desired bounds - left = sTempRect.left + (sTempRect.width() - width) / 2; - } - if (height >= sTempRect.height()) { - top = sTempRect.top + (sTempRect.height() - height) / 2; + left = bounds.left + (bounds.width() - width) / 2; } int folderPivotX = width / 2 + (centeredLeft - left); diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index 8d534d2fe..85e3c3333 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -39,6 +39,7 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; import com.android.launcher3.DropTarget.DragObject; @@ -60,17 +61,17 @@ public class FolderIcon extends FrameLayout implements FolderListener { private StylusEventHelper mStylusEventHelper; // The number of icons to display in the - public static final int NUM_ITEMS_IN_PREVIEW = 3; + public static final int NUM_ITEMS_IN_PREVIEW = 4; private static final int CONSUMPTION_ANIMATION_DURATION = 100; private static final int DROP_IN_ANIMATION_DURATION = 400; private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; private static final int FINAL_ITEM_ANIMATION_DURATION = 200; // The degree to which the inner ring grows when accepting drop - private static final float INNER_RING_GROWTH_FACTOR = 0.15f; + private static final float INNER_RING_GROWTH_FACTOR = 0.0f; // The degree to which the outer ring is scaled in its natural state - private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; + private static final float OUTER_RING_GROWTH_FACTOR = 0.1f; // The amount of vertical spread between items in the stack [0...1] private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; @@ -90,7 +91,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public static Drawable sSharedFolderLeaveBehind = null; - @Thunk ImageView mPreviewBackground; + @Thunk View mPreviewBackground; @Thunk BubbleTextView mFolderName; FolderRingAnimator mFolderRingAnimator = null; @@ -161,11 +162,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; // Offset the preview background to center this view accordingly - icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); + icon.mPreviewBackground = icon.findViewById(R.id.preview_background); lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams(); - lp.topMargin = grid.folderBackgroundOffset; - lp.width = grid.folderIconSizePx; - lp.height = grid.folderIconSizePx; + lp.width = grid.iconSizePx; + lp.height = grid.iconSizePx; + icon.mPreviewBackground.setLayoutParams(lp); icon.setTag(folderInfo); icon.setOnClickListener(launcher); @@ -183,6 +184,58 @@ public class FolderIcon extends FrameLayout implements FolderListener { folderInfo.addListener(icon); icon.setOnFocusChangeListener(launcher.mFocusHandler); + icon.setDrawingCacheEnabled(true); + + // get dimen for the icon size + // padding is equal to 1/8 of icon size + // padding gets used at start and end accounting for 2/8 + // small icons are separated by 1/2 padding + // Total padding equals 2.5/8 leaving 5.5/8 for icons + // 5.5/8 remaining, divided by 2 equals 2.75 for each small icon + int padding = grid.iconSizePx / 8; + int smallIconSize = (int) (padding * 2.75); + + for (int i = NUM_ITEMS_IN_PREVIEW; i >= 0; i--) { + ImageView appIcon = null; + int marginLeft = 0, marginRight = 0, marginTop = 0, marginBottom = 0; + switch(i) { + case 0: + appIcon = (ImageView) icon.findViewById(R.id.app_0); + marginLeft = padding; + marginTop = padding; + break; + case 1: + appIcon = (ImageView) icon.findViewById(R.id.app_1); + marginTop = padding; + marginRight = padding; + break; + case 2: + appIcon = (ImageView) icon.findViewById(R.id.app_2); + marginBottom = padding; + marginLeft = padding; + break; + case 3: + appIcon = (ImageView) icon.findViewById(R.id.app_3); + marginBottom = padding; + marginRight = padding; + break; + } + + if (appIcon != null) { + RelativeLayout.LayoutParams layoutParams + = (RelativeLayout.LayoutParams) appIcon.getLayoutParams(); + + layoutParams.width = smallIconSize; + layoutParams.height = smallIconSize; + layoutParams.leftMargin = marginLeft; + layoutParams.rightMargin = marginRight; + layoutParams.topMargin = marginTop; + layoutParams.bottomMargin = marginBottom; + + appIcon.setLayoutParams(layoutParams); + } + } + return icon; } @@ -220,11 +273,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { } DeviceProfile grid = launcher.getDeviceProfile(); - sPreviewSize = grid.folderIconSizePx; + sPreviewSize = grid.iconSizePx; sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); - sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer); - sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip); - sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); + sSharedOuterRingDrawable = res.getDrawable(R.drawable.folder_fill_highlight); + sSharedInnerRingDrawable = null; + sSharedFolderLeaveBehind = res.getDrawable(R.drawable.folder_bg); sStaticValuesDirty = false; } } @@ -375,7 +428,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { item = (ShortcutInfo) mDragInfo; } mFolder.beginExternalDrag(item); - mLauncher.openFolder(FolderIcon.this); + mFolderRingAnimator.mCellLayout.hideFolderAccept(mFolderRingAnimator); + + int[] folderTouchXY = new int[2]; + mFolder.getLocationOnScreen(folderTouchXY); + int[] folderTouchXYOffset = {folderTouchXY[0] + mFolder.getWidth() / 2, + folderTouchXY[1] + mFolder.getHeight() / 2}; + + mLauncher.openFolder(FolderIcon.this, folderTouchXYOffset); } }; @@ -482,6 +542,15 @@ public class FolderIcon extends FrameLayout implements FolderListener { if (d.dragInfo instanceof AppInfo) { // Came from all apps -- make a copy item = ((AppInfo) d.dragInfo).makeShortcut(); + } else if (d.dragInfo instanceof FolderInfo) { + FolderInfo folder = (FolderInfo) d.dragInfo; + mFolder.notifyDrop(); + for (ShortcutInfo fItem : folder.contents) { + onDrop(fItem, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); + } + mLauncher.removeFolder(folder); + LauncherModel.deleteItemFromDatabase(mLauncher, folder); + return; } else { item = (ShortcutInfo) d.dragInfo; } @@ -511,7 +580,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; - mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset; + mPreviewOffsetY = grid.folderBackgroundOffset; } } @@ -622,13 +691,35 @@ public class FolderIcon extends FrameLayout implements FolderListener { int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); if (!mAnimating) { - for (int i = nItemsInPreview - 1; i >= 0; i--) { - v = (TextView) items.get(i); - if (!mHiddenItems.contains(v.getTag())) { - d = getTopDrawable(v); - mParams = computePreviewItemDrawingParams(i, mParams); - mParams.drawable = d; - drawPreviewItem(canvas, mParams); + for (int i = NUM_ITEMS_IN_PREVIEW; i >= 0; i--) { + d = null; + if (i < items.size()) { + v = (TextView) items.get(i); + if (!mHiddenItems.contains(v.getTag())) { + d = getTopDrawable(v); + mParams = computePreviewItemDrawingParams(i, mParams); + mParams.drawable = d; + } + } + + ImageView appIcon = null; + switch(i) { + case 0: + appIcon = (ImageView) findViewById(R.id.app_0); + break; + case 1: + appIcon = (ImageView) findViewById(R.id.app_1); + break; + case 2: + appIcon = (ImageView) findViewById(R.id.app_2); + break; + case 3: + appIcon = (ImageView) findViewById(R.id.app_3); + break; + } + + if (appIcon != null) { + appIcon.setImageDrawable(d); } } } else { @@ -645,10 +736,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { final Runnable onCompleteRunnable) { final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); - float iconSize = mLauncher.getDeviceProfile().iconSizePx; - final float scale0 = iconSize / d.getIntrinsicWidth() ; - final float transX0 = (mAvailableSpaceInPreview - iconSize) / 2; - final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2 + getPaddingTop(); + final float scale0 = 1.0f; + final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2; + final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop(); mAnimParams.drawable = d; ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); @@ -675,7 +765,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void onAnimationEnd(Animator animation) { mAnimating = false; if (onCompleteRunnable != null) { - onCompleteRunnable.run(); + mLauncher.runOnUiThread(onCompleteRunnable); } } }); diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index aea21c95b..32d752ac0 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -103,7 +103,6 @@ public class FolderInfo extends ItemInfo { super.onAddToDatabase(context, values); values.put(LauncherSettings.Favorites.TITLE, title.toString()); values.put(LauncherSettings.Favorites.OPTIONS, options); - } void addListener(FolderListener listener) { diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index 7343bf686..6400a0f89 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -24,10 +24,14 @@ public class InsettableFrameLayout extends FrameLayout implements if (child instanceof Insettable) { ((Insettable) child).setInsets(newInsets); } else if (!lp.ignoreInsets) { - lp.topMargin += (newInsets.top - oldInsets.top); + if (!lp.ignoreTopInsets) { + lp.topMargin += (newInsets.top - oldInsets.top); + } lp.leftMargin += (newInsets.left - oldInsets.left); lp.rightMargin += (newInsets.right - oldInsets.right); - lp.bottomMargin += (newInsets.bottom - oldInsets.bottom); + if (!lp.ignoreBottomInsets) { + lp.bottomMargin += (newInsets.bottom - oldInsets.bottom); + } } child.setLayoutParams(lp); } @@ -65,6 +69,8 @@ public class InsettableFrameLayout extends FrameLayout implements public static class LayoutParams extends FrameLayout.LayoutParams { boolean ignoreInsets = false; + boolean ignoreTopInsets = false; + boolean ignoreBottomInsets = false; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); @@ -72,6 +78,10 @@ public class InsettableFrameLayout extends FrameLayout implements R.styleable.InsettableFrameLayout_Layout); ignoreInsets = a.getBoolean( R.styleable.InsettableFrameLayout_Layout_layout_ignoreInsets, false); + ignoreTopInsets = a.getBoolean( + R.styleable.InsettableFrameLayout_Layout_layout_ignoreTopInsets, false); + ignoreBottomInsets = a.getBoolean( + R.styleable.InsettableFrameLayout_Layout_layout_ignoreBottomInsets, false); a.recycle(); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1f843cb70..6faea2084 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -16,6 +16,7 @@ package com.android.launcher3; +import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -150,6 +151,8 @@ public class Launcher extends Activity private static final int REQUEST_BIND_APPWIDGET = 11; private static final int REQUEST_RECONFIGURE_APPWIDGET = 12; + private static final int REQUEST_PERMISSION_CALL_PHONE = 13; + private static final int WORKSPACE_BACKGROUND_GRADIENT = 0; private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1; private static final int WORKSPACE_BACKGROUND_BLACK = 2; @@ -867,6 +870,24 @@ public class Launcher extends Activity /** @Override for MNC */ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == REQUEST_PERMISSION_CALL_PHONE && sPendingAddItem != null + && sPendingAddItem.requestCode == REQUEST_PERMISSION_CALL_PHONE) { + View v = null; + CellLayout layout = getCellLayout(sPendingAddItem.container, sPendingAddItem.screenId); + if (layout != null) { + v = layout.getChildAt(sPendingAddItem.cellX, sPendingAddItem.cellY); + } + Intent intent = sPendingAddItem.intent; + sPendingAddItem = null; + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + startActivity(v, intent, null); + } else { + // TODO: Show a snack bar with link to settings + Toast.makeText(this, getString(R.string.msg_no_phone_permission, + getString(R.string.app_name)), Toast.LENGTH_SHORT).show(); + } + } if (mLauncherCallbacks != null) { mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -1821,6 +1842,10 @@ public class Launcher extends Activity return mHotseat; } + public View getPageIndicator() { + return mPageIndicators; + } + public ViewGroup getOverviewPanel() { return mOverviewPanel; } @@ -2688,6 +2713,11 @@ public class Launcher extends Activity final FolderInfo info = folderIcon.getFolderInfo(); Folder openFolder = mWorkspace.getFolderForTag(info); + int[] folderTouchXY = new int[2]; + v.getLocationOnScreen(folderTouchXY); + int[] folderTouchXYOffset = {folderTouchXY[0] + v.getWidth() / 2, + folderTouchXY[1] + v.getHeight() / 2}; + // If the folder info reports that the associated folder is open, then verify that // it is actually opened. There have been a few instances where this gets out of sync. if (info.opened && openFolder == null) { @@ -2700,7 +2730,7 @@ public class Launcher extends Activity // Close any open folder closeFolder(); // Open the requested folder - openFolder(folderIcon); + openFolder(folderIcon, folderTouchXYOffset); } else { // Find the open folder... int folderScreen; @@ -2712,7 +2742,7 @@ public class Launcher extends Activity // Close any folder open on the current screen closeFolder(); // Pull the folder onto this screen - openFolder(folderIcon); + openFolder(folderIcon, folderTouchXYOffset); } } } @@ -2926,6 +2956,22 @@ public class Launcher extends Activity } return true; } catch (SecurityException e) { + if (Utilities.ATLEAST_MARSHMALLOW && tag instanceof ItemInfo) { + // Due to legacy reasons, direct call shortcuts require Launchers to have the + // corresponding permission. Show the appropriate permission prompt if that + // is the case. + if (intent.getComponent() == null + && Intent.ACTION_CALL.equals(intent.getAction()) + && checkSelfPermission(Manifest.permission.CALL_PHONE) != + PackageManager.PERMISSION_GRANTED) { + // TODO: Rename sPendingAddItem to a generic name. + sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent, + 0, (ItemInfo) tag); + requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, + REQUEST_PERMISSION_CALL_PHONE); + return false; + } + } Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Launcher does not have the permission to launch " + intent + ". Make sure to create a MAIN intent-filter for the corresponding activity " + @@ -3063,8 +3109,13 @@ public class Launcher extends Activity * * @param folderInfo The FolderInfo describing the folder to open. */ - public void openFolder(FolderIcon folderIcon) { + public void openFolder(FolderIcon folderIcon, int[] folderTouch) { Folder folder = folderIcon.getFolder(); + + if (folder.getState() == Folder.STATE_ANIMATING) { + return; + } + Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null; if (openFolder != null && openFolder != folder) { // Close any open folder before opening a folder. @@ -3087,8 +3138,8 @@ public class Launcher extends Activity Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + folder.getParent() + ")."); } - folder.animateOpen(); - growAndFadeOutFolderIcon(folderIcon); + folder.animateOpen(getWorkspace(), folderTouch); + /*growAndFadeOutFolderIcon(folderIcon);*/ // Notify the accessibility manager that this folder "window" has appeared and occluded // the workspace items @@ -3107,17 +3158,21 @@ public class Launcher extends Activity } public void closeFolder(Folder folder) { + closeFolder(folder, true); + } + + public void closeFolder(Folder folder, boolean animate) { folder.getInfo().opened = false; ViewGroup parent = (ViewGroup) folder.getParent().getParent(); if (parent != null) { FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo); - shrinkAndFadeInFolderIcon(fi); + /*shrinkAndFadeInFolderIcon(fi);*/ if (fi != null) { ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true; } } - folder.animateClosed(); + folder.animateClosed(animate); // Notify the accessibility manager that this folder "window" has disappeard and no // longer occludeds the workspace items diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 8a5804f34..5cde2e588 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -40,7 +40,7 @@ public class LauncherSettings { *

Type: TEXT

*/ public static final String TITLE = "title"; - + /** * The Intent URL of the gesture, describing what it points to. This * value is given to {@link android.content.Intent#parseUri(String, int)} to create -- cgit v1.2.3