Skip to content

Instantly share code, notes, and snippets.

@Volcanoscar
Forked from luca020400/floating.patch
Created February 17, 2017 06:31
Show Gist options
  • Save Volcanoscar/5634139f32077e49b1c1e958f82959c6 to your computer and use it in GitHub Desktop.
Save Volcanoscar/5634139f32077e49b1c1e958f82959c6 to your computer and use it in GitHub Desktop.
From 34b73571a4d322ab2afe59a80912337783f29fb7 Mon Sep 17 00:00:00 2001
From: BigBrother1984 <[email protected]>
Date: Thu, 10 Apr 2014 18:07:26 +0200
Subject: [PATCH] base: floating window
Squash commits from aospa
Change-Id: I81ba0bfaf8e6f29388e07beb78197dd316a11de7
Signed-off-by: jrizzoli <[email protected]>
---
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9568897..89b2373 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -28,11 +28,13 @@
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.policy.PolicyManager;
+import com.android.internal.widget.FloatingWindowView;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.admin.DevicePolicyManager;
+import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -49,8 +51,10 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
+import android.graphics.Color;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
@@ -72,18 +76,22 @@
import android.util.EventLog;
import android.util.Log;
import android.util.PrintWriterPrinter;
+import android.util.TypedValue;
import android.util.Slog;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
@@ -94,6 +102,9 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
+import android.widget.FrameLayout;
+
+import com.android.internal.util.paranoid.ColorUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -101,6 +112,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* An activity is a single, focused thing that the user can do. Almost all
@@ -680,6 +692,9 @@
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
+ private static final String SAVED_WINDOW_LAYOUT_IDS_KEY = "android:savedWindowLayoutIds";
+ private static final String SAVED_WINDOW_LAYOUT_TAG = "android:savedWindowLayout";
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -733,6 +748,7 @@
/*package*/ boolean mVisibleFromServer = false;
/*package*/ boolean mVisibleFromClient = true;
/*package*/ ActionBar mActionBar = null;
+ /*package*/ WindowDecorActionBar mDecorActionBar = null;
private boolean mEnableDefaultActionBarUp;
private VoiceInteractor mVoiceInteractor;
@@ -796,6 +812,52 @@
ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
+
+ private Rect mOriginalBounds;
+ private boolean mIsSplitView;
+
+ private final int UNKNOWN = -10000;
+ private final int SNAP_NONE = 0;
+ private final int SNAP_LEFT = 1;
+ private final int SNAP_TOP = 2;
+ private final int SNAP_RIGHT = 3;
+ private final int SNAP_BOTTOM = 4;
+ private final int MOVE_MAX_RANGE = 10;
+
+ private Runnable mRunnable;
+ private Runnable mLayoutRunnable;
+ private int mRange = 100;
+ private int mSensitivity = 50;
+ private int mSnap = SNAP_NONE;
+ private int[] mSnapParam = new int[3]; // w,h,g
+ private int[] mOldParam = new int[2]; // w,h
+ private boolean mSnapped;
+ private int[] mOldLayout;
+ private int[] mLastLayout;
+ private boolean mTimeoutRunning;
+ private boolean mTimeoutDone;
+ private boolean mRestorePosition;
+ private boolean mChangedPreviousRange;
+ private float[] mPreviousRange = new float[2];
+
+ private Float screenX ;
+ private Float screenY ;
+ private Float viewX ;
+ private Float viewY ;
+ private Float leftFromScreen ;
+ private Float topFromScreen ;
+ private int mCurrentScreenHeight;
+ private int mCurrentScreenWidth;
+ private int mPreviousOrientation;
+ private int mAppMinimumWidth;
+ private int mAppMinimumHeight;
+ private int mAppFloatViewWidth;
+ private int mAppFloatViewHeight;
+ private boolean mChangedFlags = false;
+ private boolean isAlreadyAttachToWindow = false;
+ public boolean mIsFullscreenApp = false;
+ private ScaleGestureDetector mScaleGestureDetector;
+ private FloatingWindowView mFloatingWindowView;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -939,6 +1001,9 @@
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
+ mPreviousOrientation = getResources().getConfiguration().orientation;
+ mScaleGestureDetector = new ScaleGestureDetector(getApplicationContext(), mScaleGestureListener);
+ mScaleGestureDetector.setQuickScaleEnabled(false);
}
/**
@@ -977,6 +1042,7 @@
final void performRestoreInstanceState(Bundle savedInstanceState) {
onRestoreInstanceState(savedInstanceState);
restoreManagedDialogs(savedInstanceState);
+ restoreManagedWindowLayout(savedInstanceState);
}
/**
@@ -1085,6 +1151,54 @@
}
}
+ private void restoreManagedWindowLayout(Bundle savedInstanceState) {
+ final Bundle b = savedInstanceState.getBundle(SAVED_WINDOW_LAYOUT_TAG);
+ if (b == null) {
+ return;
+ }
+
+ if (mWindow == null) {
+ return;
+ }
+
+ if (!mWindow.mIsFloatingWindow) {
+ return;
+ }
+
+ final int[] ids = b.getIntArray(SAVED_WINDOW_LAYOUT_IDS_KEY);
+ final int x = ids[0];
+ final int y = ids[1];
+ final int width = ids[2];
+ final int height = ids[3];
+ mSnap = ids[4];
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ params.x = x;
+ params.y = y;
+ switch (mSnap) {
+ case SNAP_LEFT:
+ params.width = (mCurrentScreenWidth / 2);
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ break;
+ case SNAP_RIGHT:
+ params.width = (mCurrentScreenWidth / 2);
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ break;
+ case SNAP_TOP:
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = (mCurrentScreenHeight / 2);
+ break;
+ case SNAP_BOTTOM:
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = (mCurrentScreenHeight / 2);
+ break;
+ case SNAP_NONE:
+ params.width = width;
+ params.height = height;
+ break;
+ }
+ mWindow.setAttributes(params);
+ }
+
private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) {
final Dialog dialog = onCreateDialog(dialogId, args);
if (dialog == null) {
@@ -1156,6 +1270,7 @@
*/
protected void onStart() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
+ setupFloatingActionBar(false);
mCalled = true;
if (!mLoadersStarted) {
@@ -1169,6 +1284,87 @@
}
getApplication().dispatchActivityStarted(this);
+ }
+
+ private void setupFloatingActionBar(boolean reload) {
+ if (mWindow == null) {
+ return;
+ }
+
+ if (!mWindow.mIsFloatingWindow) {
+ return;
+ }
+
+ if (mWindow.peekDecorView() == null) {
+ return;
+ }
+
+ mWindow.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+
+ FrameLayout decorFloatingView = (FrameLayout) mWindow.peekDecorView().getRootView();
+ if (decorFloatingView == null) {
+ return;
+ }
+ if (!reload) {
+ decorFloatingView.setFitsSystemWindows(true);
+ mFloatingWindowView = new FloatingWindowView(this, getActionBarHeight(true));
+ decorFloatingView.addView(mFloatingWindowView, -1, FloatingWindowView.getParams());
+ decorFloatingView.setTagInternal(android.R.id.extractArea, mFloatingWindowView);
+ changeTitleBarColor();
+ } else {
+ mFloatingWindowView = (FloatingWindowView) decorFloatingView.getTag(android.R.id.extractArea);
+ changeTitleBarColor();
+ decorFloatingView.bringChildToFront(mFloatingWindowView);
+ }
+ }
+
+ private void changeFloatingWindowColor(int bg_color, int ic_color) {
+ mFloatingWindowView.setFloatingBackgroundColor(bg_color);
+ mFloatingWindowView.setFloatingColorFilter(ic_color);
+ }
+
+
+ private void changeTitleBarColor() {
+ if (mFloatingWindowView != null) {
+ try {
+ final PackageManager pm = getPackageManager();
+ // Getting current task packagename
+ final ActivityManager am = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);
+ List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+ ActivityManager.RunningTaskInfo task = tasks.get(0); // current task
+ ComponentName rootActivity = task.baseActivity;
+ String appPackageName = rootActivity.getPackageName();
+ // Get theme from packagename
+ final Resources res = pm.getResourcesForApplication(appPackageName);
+ final int[] attrs = new int[] {
+ res.getIdentifier("colorPrimary", "attr", appPackageName),
+ android.R.attr.colorPrimary
+ };
+
+ final Resources.Theme theme = res.newTheme();
+ final ComponentName cn = pm.getLaunchIntentForPackage(appPackageName).getComponent();
+ theme.applyStyle(pm.getActivityInfo(cn, 0).theme, false);
+ // Obtain the colorPrimary color from the attrs
+ TypedArray a = theme.obtainStyledAttributes(attrs);
+ // Do something with the color
+ final int colorPrimary = a.getColor(0, a.getColor(1, Color.WHITE));
+ // Make sure you recycle the TypedArray
+ a.recycle();
+ a = null;
+ int iconTint;
+
+ if (ColorUtils.isBrightColor(colorPrimary)) {
+ iconTint = Color.BLACK;
+ } else {
+ iconTint = Color.WHITE;
+ }
+ changeFloatingWindowColor(colorPrimary, iconTint);
+ } catch (final NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
}
/**
@@ -1238,6 +1434,7 @@
final Window win = getWindow();
if (win != null) win.makeActive();
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+ setupFloatingActionBar(true);
mCalled = true;
}
@@ -1298,6 +1495,7 @@
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
+ saveManagedWindowLayout(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
@@ -1424,6 +1622,27 @@
outState.putBundle(SAVED_DIALOGS_TAG, dialogState);
}
+ private void saveManagedWindowLayout(Bundle outState) {
+ if (mWindow == null) {
+ return;
+ }
+
+ if (!mWindow.mIsFloatingWindow) {
+ return;
+ }
+
+ Bundle windowLayoutState = new Bundle();
+
+ int[] ids = new int[5];
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ ids[0] = params.x;
+ ids[1] = params.y;
+ ids[2] = params.width;
+ ids[3] = params.height;
+ ids[4] = mSnap;
+ windowLayoutState.putIntArray(SAVED_WINDOW_LAYOUT_IDS_KEY, ids);
+ outState.putBundle(SAVED_WINDOW_LAYOUT_TAG, windowLayoutState);
+ }
/**
* Called as part of the activity lifecycle when an activity is going into
@@ -1604,8 +1823,8 @@
*/
protected void onDestroy() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
+ sendAppEndBroadcast();
mCalled = true;
-
// dismiss any dialogs we are managing.
if (mManagedDialogs != null) {
final int numDialogs = mManagedDialogs.size();
@@ -1686,6 +1905,51 @@
if (mWindow != null) {
// Pass the configuration changed event to the window
mWindow.onConfigurationChanged(newConfig);
+ if (mWindow.mIsFloatingWindow) {
+ refreshAppLayoutSize();
+ Configuration config = getResources().getConfiguration();
+ if (config.orientation != mPreviousOrientation) {
+ mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
+ if (!isUnSnap()) {
+ requestChangingFlagsLayout();
+ }
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ switch (mSnap) {
+ case SNAP_LEFT:
+ params.width = (mCurrentScreenWidth / 2);
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.x = 0;
+ params.y = 0;
+ break;
+ case SNAP_RIGHT:
+ params.width = (mCurrentScreenWidth / 2);
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.x = (mCurrentScreenWidth / 2);
+ params.y = 0;
+ break;
+ case SNAP_TOP:
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = (mCurrentScreenHeight / 2);
+ params.x = 0;
+ params.y = 0;
+ break;
+ case SNAP_BOTTOM:
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = (mCurrentScreenHeight / 2);
+ params.x = 0;
+ params.y = (mCurrentScreenHeight / 2);
+ break;
+ case SNAP_NONE:
+ int width = params.width;
+ int height = params.height;
+ params.width = height;
+ params.height = width;
+ break;
+ }
+ mWindow.setAttributes(params);
+ mPreviousOrientation = config.orientation;
+ }
+ }
}
if (mActionBar != null) {
@@ -2736,14 +3000,550 @@
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN)
onUserInteraction();
+ if (mWindow.mIsFloatingWindow) {
+ if (isUnSnap()) {
+ mScaleGestureDetector.onTouchEvent(ev);
+ }
+ int actionBarHeight = getActionBarHeight(false);
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ setTouchViewDown(ev.getX(), ev.getY());
+ onUserInteraction();
+ updateFocusApp();
+ if (viewY < actionBarHeight) {
+ if (!mChangedPreviousRange) {
+ setPreviousTouchRange(ev.getRawX(), ev.getRawY());
+ mChangedPreviousRange = true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (viewY < actionBarHeight) {
+ changeFlagsLayoutParams();
+ setTouchViewMove(ev.getRawX(), ev.getRawY());
+ if (mRestorePosition && moveRangeAboveLimit(ev)) {
+ restoreOldPosition();
+ }
+ showSnap((int) ev.getRawX(), (int) ev.getRawY());
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (viewY < actionBarHeight) {
+ mChangedFlags = false;
+ finishSnap(isValidSnap() && mTimeoutDone);
+ discardTimeout();
+ mChangedPreviousRange = false;
+ }
+ break;
+ }
+ } else {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ onUserInteraction();
+ }
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
+
+ /**
+ * @hide
+ */
+ public void setTouchViewDown(float x , float y) {
+ viewX = x;
+ viewY = y;
+ }
+
+ /**
+ * @hide
+ */
+ public void setPreviousTouchRange(float x , float y) {
+ mPreviousRange[0] = x;
+ mPreviousRange[1] = y;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTouchViewMove(float x , float y) {
+ screenX = x;
+ screenY = y;
+ leftFromScreen = (screenX - viewX);
+ topFromScreen = (screenY - viewY);
+ initLayoutParams(leftFromScreen, topFromScreen);
+ }
+
+ /**
+ * @hide
+ */
+ public void setChangedPreviousRange(boolean needed) {
+ mChangedPreviousRange = needed;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getChangedPreviousRange() {
+ return mChangedPreviousRange;
+ }
+
+ private int getActionBarHeight(boolean smaller) {
+ ActionBar actionBar = getActionBar();
+ int actionBarHeight = (actionBar != null) ? actionBar.getHeight()
+ : getAppDimensionPixel(smaller);
+ return (smaller ? getAppDimensionPixel(smaller) : actionBarHeight);
+ }
+
+ /**
+ * @hide
+ */
+ public void updateFocusApp() {
+ IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+ try {
+ wm.notifyFloatActivityTouched(mToken, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot notify activity touched", e);
+ }
+ }
+
+ private void initLayoutParams(float x , float y) {
+ WindowManager.LayoutParams param = mWindow.getAttributes();
+ param.x = (int) x;
+ param.y = (int) y;
+ mWindow.setAttributes(param);
+ }
+
+ /**
+ * @hide
+ */
+ public void changeFlagsLayoutParams() {
+ mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
+ if (!mChangedFlags) {
+ mChangedFlags = true;
+ requestChangingFlagsLayout();
+ }
+ }
+
+ private void requestChangingFlagsLayout() {
+ mWindow.setCloseOnTouchOutside(false);
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
+ if (ActivityManager.isHighEndGfx()) {
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setChangedFlags(boolean changed) {
+ mChangedFlags = changed;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean moveRangeAboveLimit(MotionEvent event) {
+ final float x = event.getRawX();
+ final float y = event.getRawY();
+
+ boolean returnValue = false;
+ if (Math.abs(mPreviousRange[0] - x) > MOVE_MAX_RANGE) {
+ returnValue = true;
+ } else if (Math.abs(mPreviousRange[1] - y) > MOVE_MAX_RANGE) {
+ returnValue = true;
+ }
+ return returnValue;
+ }
+
+ /**
+ * @hide
+ */
+ public void showSnap(int x, int y) {
+ initSnappable(x, y);
+ calculateSnap();
+ if (isValidSnap()) {
+ setupTimeout();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void finishSnap(boolean apply) {
+ if (apply) {
+ if (saveOldPosition()) {
+ mRestorePosition = true;
+ }
+ WindowManager.LayoutParams lpp = mWindow.getAttributes();
+ lpp.width = mSnapParam[0];
+ lpp.height = mSnapParam[1];
+ int gravity = mSnapParam[2];
+ lpp.x = (gravity == Gravity.RIGHT) ? (mCurrentScreenWidth / 2) : 0;
+ lpp.y = (gravity == Gravity.BOTTOM) ? (mCurrentScreenHeight / 2) : 0;
+ mWindow.setAttributes(lpp);
+ } else {
+ mSnap = SNAP_NONE;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isValidSnap() {
+ return (mSnapParam[0] != UNKNOWN) &&
+ (mSnapParam[1] != UNKNOWN) &&
+ (mSnapParam[2] != UNKNOWN);
+ }
+
+ private boolean saveOldPosition() {
+ if (mRestorePosition) return true;
+ if (mSnapped) {
+ return (isUnSnap() || mTimeoutRunning);
+ }
+ mSnapped = true;
+ final WindowManager.LayoutParams params = mWindow.getAttributes();
+ int[] layout = { params.x, params.y, params.width, params.height };
+ mOldLayout = layout;
+ return true;
+ }
+
+ private boolean isUnSnap() {
+ return (mSnap == SNAP_NONE);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean restoreOldPosition() {
+ if (!mSnapped) return false;
+ restoreOldPositionWithoutRefresh();
+ return true;
+ }
+
+ private void restoreOldPositionWithoutRefresh() {
+ if (!mSnapped) return;
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ params.x = mOldLayout[0];
+ params.y = mOldLayout[1];
+ params.width = mOldLayout[2];
+ params.height = mOldLayout[3];
+ mWindow.setAttributes(params);
+ mSnapped = false;
+ mRestorePosition = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getRestorePosition() {
+ return mRestorePosition;
+ }
+
+ private boolean initSnappable(int x, int y) {
+ if ((Math.abs(mOldParam[0] - x) > mSensitivity) ||
+ (Math.abs(mOldParam[1] - y) > mSensitivity)) {
+ mOldParam[0] = x;
+ mOldParam[1] = y;
+ return false;
+ }
+ mOldParam[0] = x;
+ mOldParam[1] = y;
+
+ if (x < mRange) {
+ mSnap = SNAP_LEFT;
+ } else if (x > (mCurrentScreenWidth - mRange)) {
+ mSnap = SNAP_RIGHT;
+ } else if (y < mRange) {
+ mSnap = SNAP_TOP;
+ } else if (y > (mCurrentScreenHeight - mRange)) {
+ mSnap = SNAP_BOTTOM;
+ } else {
+ mSnap = SNAP_NONE;
+ return false;
+ }
+ return true;
+ }
+
+ private void refreshAppLayoutSize() {
+ final IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+
+ try {
+ Rect windowFloatViewBounds = wm.getFloatViewRect();
+ mAppFloatViewWidth = (windowFloatViewBounds.right - windowFloatViewBounds.left);
+ mAppFloatViewHeight = (windowFloatViewBounds.bottom - windowFloatViewBounds.top);
+
+ Rect windowCurrentScreenBounds = wm.getAppFullscreenViewRect();
+ mCurrentScreenWidth = (windowCurrentScreenBounds.right - windowCurrentScreenBounds.left);
+ mCurrentScreenHeight = (windowCurrentScreenBounds.bottom - windowCurrentScreenBounds.top);
+
+ Rect windowMinimumBounds = wm.getAppMinimumViewRect();
+ mAppMinimumWidth = (windowMinimumBounds.right - windowMinimumBounds.left);
+ mAppMinimumHeight = (windowMinimumBounds.bottom - windowMinimumBounds.top);
+
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not perform get size view layout", e);
+ }
+ }
+
+ private void refreshAppFloatViewSize() {
+ final IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+
+ try {
+ Rect windowBounds = wm.getFloatViewRect();
+ mAppFloatViewWidth = (windowBounds.right - windowBounds.left);
+ mAppFloatViewHeight = (windowBounds.bottom - windowBounds.top);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not perform float view layout", e);
+ }
+ }
+
+ private void refreshCurrentScreenSize() {
+ final IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+
+ try {
+ Rect windowBounds = wm.getAppFullscreenViewRect();
+ mCurrentScreenWidth = (windowBounds.right - windowBounds.left);
+ mCurrentScreenHeight = (windowBounds.bottom - windowBounds.top);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not perform get app fullscreen view layout", e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void forceSnap(int side) {
+ if (side == SNAP_NONE) {
+ restoreOldPosition();
+ return;
+ }
+ if (mSnapped) {
+ restoreOldPositionWithoutRefresh();
+ }
+ mSnap = side;
+ calculateSnap();
+ finishSnap(true);
+ }
+
+ private void calculateSnap() {
+ switch (mSnap) {
+ case SNAP_LEFT:
+ mSnapParam[0] = (mCurrentScreenWidth / 2);
+ mSnapParam[1] = ViewGroup.LayoutParams.MATCH_PARENT;
+ mSnapParam[2] = Gravity.TOP | Gravity.LEFT;
+ break;
+ case SNAP_RIGHT:
+ mSnapParam[0] = (mCurrentScreenWidth / 2);
+ mSnapParam[1] = ViewGroup.LayoutParams.MATCH_PARENT;
+ mSnapParam[2] = Gravity.RIGHT;
+ break;
+ case SNAP_TOP:
+ mSnapParam[0] = ViewGroup.LayoutParams.MATCH_PARENT;
+ mSnapParam[1] = (mCurrentScreenHeight / 2);
+ mSnapParam[2] = Gravity.TOP;
+ break;
+ case SNAP_BOTTOM:
+ mSnapParam[0] = ViewGroup.LayoutParams.MATCH_PARENT;
+ mSnapParam[1] = (mCurrentScreenHeight / 2);
+ mSnapParam[2] = Gravity.BOTTOM;
+ break;
+ case SNAP_NONE:
+ mSnapParam[0] = UNKNOWN;
+ mSnapParam[1] = UNKNOWN;
+ mSnapParam[2] = UNKNOWN;
+ break;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getTimeoutDone() {
+ return mTimeoutDone;
+ }
+
+ /**
+ * @hide
+ */
+ public void discardTimeout() {
+ mTimeoutDone = false;
+ mTimeoutRunning = false;
+ mHandler.removeCallbacks(mRunnable);
+ }
+
+ private void setupTimeout() {
+ if (mTimeoutRunning) return;
+ if (mRunnable == null) {
+ mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mTimeoutRunning = false;
+ mTimeoutDone = true;
+ }
+ }, 250);
+ }
+ };
+ }
+ mTimeoutRunning = true;
+ mHandler.postDelayed(mRunnable, 750);
+ }
+
+ private int getAppDimensionPixel(boolean smaller) {
+ float scale = getResources().getDisplayMetrics().density;
+ int sized = smaller ? 32 : 48;
+ int pixel = (int) (sized * scale + 0.5f);
+ return pixel;
+ }
+
+ private void refreshAppMinimumSize() {
+ final IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+
+ try {
+ Rect windowBounds = wm.getAppMinimumViewRect();
+ mAppMinimumWidth = (windowBounds.right - windowBounds.left);
+ mAppMinimumHeight = (windowBounds.bottom - windowBounds.top);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not perform get app minimum view layout", e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void sendAppLaunchBroadcast() {
+ Intent appIntent = new Intent(Intent.ACTION_ACTIVITY_LAUNCH_DETECTOR);
+ appIntent.putExtra("packagename", getPackageName());
+ appIntent.putExtra("packagetoken", mToken);
+ appIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ sendBroadcastAsUser(appIntent, UserHandle.CURRENT_OR_SELF);
+ moveTaskToBack(true);
+ }
+
+ private void sendAppEndBroadcast() {
+ Intent endIntent = new Intent(Intent.ACTION_ACTIVITY_END_DETECTOR);
+ endIntent.putExtra("packagename", getPackageName());
+ endIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ sendBroadcastAsUser(endIntent, UserHandle.CURRENT_OR_SELF);
+ }
+
+ private void setProgressLayoutApp() {
+ if (mLayoutRunnable == null) {
+ mLayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ setCurrentLayoutApp();
+ }
+ };
+ }
+ mHandler.postDelayed(mLayoutRunnable, 500);
+ }
+
+ private void discardLayoutProgress() {
+ mHandler.removeCallbacks(mLayoutRunnable);
+ }
+
+ private void setCurrentLayoutApp() {
+ if (isUnSnap()) {
+ final WindowManager.LayoutParams params = mWindow.getAttributes();
+ final int width = params.width;
+ final int height = params.height;
+ if (width >= mCurrentScreenWidth) {
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else if (width <= mAppMinimumWidth) {
+ params.width = mAppMinimumWidth;
+ }
+ if (height >= mCurrentScreenHeight) {
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else if (height <= mAppMinimumHeight) {
+ params.height = mAppMinimumHeight;
+ }
+ mWindow.setAttributes(params);
+ }
+ mHandler.removeCallbacks(mLayoutRunnable);
+ }
+
+ /**
+ * @hide
+ */
+ public void setFullscreenApp() {
+ if (mIsFullscreenApp) {
+ return;
+ }
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ int[] layout = { params.x, params.y, params.width, params.height };
+ mLastLayout = layout;
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mWindow.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+ mWindow.setAttributes(params);
+ mIsFullscreenApp = true;
+ }
+
+ /**
+ * @hide
+ */
+ public void restorePreviousLayoutApp() {
+ if (!mIsFullscreenApp) {
+ return;
+ }
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ params.x = mLastLayout[0];
+ params.y = mLastLayout[1];
+ params.width = mLastLayout[2];
+ params.height = mLastLayout[3];
+ mWindow.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+ mWindow.setAttributes(params);
+ mIsFullscreenApp = false;
+ }
+
+ private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
+ = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+
+ private float lastSpanX;
+ private float lastSpanY;
+ private WindowManager.LayoutParams params;
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+ discardLayoutProgress();
+ lastSpanX = scaleGestureDetector.getCurrentSpanX();
+ lastSpanY = scaleGestureDetector.getCurrentSpanY();
+ params = mWindow.getAttributes();
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+ float spanX = scaleGestureDetector.getCurrentSpanX();
+ float spanY = scaleGestureDetector.getCurrentSpanY();
+ float newWidth = spanX / lastSpanX * (float) params.width;
+ float newHeight = spanY / lastSpanY * (float) params.height;
+ params.width = (int) newWidth;
+ params.height = (int) newHeight;
+ mWindow.setAttributes(params);
+ lastSpanX = spanX;
+ lastSpanY = spanY;
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ setProgressLayoutApp();
+ }
+ };
/**
* Called to process trackball events. You can override this to
@@ -4651,6 +5451,11 @@
}
}
+ public void finishFloating() {
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+ mMainThread.performFinishFloating();
+ }
+
/**
* Call this when your activity is done and should be closed. The
* ActivityResult is propagated back to whoever launched you via
@@ -5929,7 +6734,10 @@
mFragments.attachActivity(this, mContainer, null);
- mWindow = PolicyManager.makeNewWindow(this);
+ if (makeNewWindow(context, intent, info)) {
+ parent = null;
+ }
+
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
@@ -5974,6 +6782,64 @@
mCurrentConfig = config;
}
+ private boolean makeNewWindow(Context context, Intent intent, ActivityInfo info) {
+ boolean floating = (intent.getFlags() & Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW;
+ if (intent != null && floating) {
+
+ TypedArray styleArray = context.obtainStyledAttributes(info.theme, com.android.internal.R.styleable.Window);
+ TypedValue backgroundValue = styleArray.peekValue(com.android.internal.R.styleable.Window_windowBackground);
+
+ // Apps that have no title don't need no title bar
+ TypedValue outValue = new TypedValue();
+ boolean result = styleArray.getValue(com.android.internal.R.styleable.Window_windowNoTitle, outValue);
+
+ if (backgroundValue != null && backgroundValue.toString().contains("light")) {
+ context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindowLight, true);
+ } else {
+ context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindow, true);
+ }
+
+ // Create our new window
+ mWindow = PolicyManager.makeNewWindow(this);
+ mWindow.mIsFloatingWindow = true;
+ if (!isAlreadyAttachToWindow) {
+ isAlreadyAttachToWindow = true;
+ mWindow.setCloseOnTouchOutsideIfNotSet(true);
+ mWindow.setGravity(Gravity.CENTER);
+ // Scale it
+ scaleFloatingWindow();
+ }
+
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID) {
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
+ WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ params.alpha = 1f;
+ params.dimAmount = 0.25f;
+ }
+ mWindow.setAttributes(params);
+
+ refreshAppLayoutSize();
+ return true;
+ } else {
+ mWindow = PolicyManager.makeNewWindow(this);
+ return false;
+ }
+ }
+
+ private void scaleFloatingWindow() {
+ final IWindowManager wm = (IWindowManager) WindowManagerGlobal.getWindowManagerService();
+
+ try {
+ Rect windowBounds = wm.getFloatViewRect();
+ mWindow.setLayout(windowBounds.right - windowBounds.left,
+ windowBounds.bottom - windowBounds.top);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not perform float view layout", e);
+ }
+ }
+
/** @hide */
public final IBinder getActivityToken() {
return mParent != null ? mParent.getActivityToken() : mToken;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d2cc60c..637de6f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -803,6 +803,16 @@
return true;
}
+ case GET_ACTIVITY_FOR_TASK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int task = data.readInt();
+ boolean onlyRoot = data.readInt() != 0;
+ IBinder res = getActivityForTask(task, onlyRoot);
+ reply.writeNoException();
+ reply.writeStrongBinder(res);
+ return true;
+ }
+
case GET_CONTENT_PROVIDER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
@@ -3323,6 +3333,21 @@
data.recycle();
reply.recycle();
}
+
+ public IBinder getActivityForTask(int task, boolean onlyRoot) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(task);
+ data.writeInt(onlyRoot ? 1 : 0);
+ mRemote.transact(GET_ACTIVITY_FOR_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ IBinder res = reply.readStrongBinder();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 67f6d7e..971f069 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2184,6 +2184,18 @@
return mActivities.get(token).activity;
}
+ protected void performFinishFloating() {
+ synchronized (mPackages) {
+ Activity a = null;
+ for (ActivityClientRecord ar : mActivities.values()) {
+ a = ar.activity;
+ if (a != null && !a.mFinished && a.getWindow() != null && a.getWindow().mIsFloatingWindow) {
+ a.finish();
+ }
+ }
+ }
+ }
+
public final void sendActivityResult(
IBinder token, String id, int requestCode,
int resultCode, Intent data) {
@@ -3013,12 +3025,12 @@
r.state = null;
r.persistentState = null;
} catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
+ /*if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to resume activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
- }
+ }*/
}
}
return r;
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9d15783..75943c2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -143,6 +143,7 @@
public void setFocusedStack(int stackId) throws RemoteException;
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
+ public IBinder getActivityForTask(int task, boolean onlyRoot) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
String name, int userId, boolean stable) throws RemoteException;
public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token)
@@ -757,8 +758,8 @@
int GET_HOME_ACTIVITY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+183;
int GET_ACTIVITY_DISPLAY_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+184;
int DELETE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+185;
-
-
+ /* FLOAT VIEW */
+ int GET_ACTIVITY_FOR_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+200;
// Start of L transactions
int GET_TAG_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+210;
int START_USER_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+211;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index ad2b61f..c7966a3 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1546,10 +1546,18 @@
}
}
try {
+ // we must resolve if the last intent in the stack is floating to give the flag to the previous
+ boolean floating = false;
+ if (intents.length > 0) {
+ floating = (intents[intents.length - 1].getFlags()&Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW;
+ }
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
intents[i].migrateExtraStreamToClipData();
intents[i].prepareToLeaveProcess();
+ if (floating) {
+ intents[i].addFlags(Intent.FLAG_FLOATING_WINDOW);
+ }
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index 0077db1..d30872e 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -63,6 +63,7 @@
private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
private final Context mSourceContext;
+ private boolean mFirstTaskOnHome = true;
private TaskStackBuilder(Context a) {
mSourceContext = a;
@@ -77,6 +78,10 @@
*/
public static TaskStackBuilder create(Context context) {
return new TaskStackBuilder(context);
+ }
+
+ public void setTaskOnHome(boolean firstTaskOnHome) {
+ mFirstTaskOnHome = firstTaskOnHome;
}
/**
@@ -301,9 +306,16 @@
Intent[] intents = new Intent[mIntents.size()];
if (intents.length == 0) return intents;
- intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ Intent newIntent = new Intent(mIntents.get(0));
+ newIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ if (mFirstTaskOnHome) {
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ }
+
+ intents[0] = newIntent;
for (int i = 1; i < intents.length; i++) {
intents[i] = new Intent(mIntents.get(i));
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0b8f76c..35a5a13 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -696,6 +696,20 @@
public static final String ACTION_PICK = "android.intent.action.PICK";
/**
+ * Registered and foreground services only
+ * @hide
+ */
+ public static final String ACTION_ACTIVITY_LAUNCH_DETECTOR =
+ "android.intent.action.ACTIVITY_LAUNCH_DETECTOR";
+
+ /**
+ * Registered and foreground services only
+ * @hide
+ */
+ public static final String ACTION_ACTIVITY_END_DETECTOR =
+ "android.intent.action.ACTIVITY_END_DETECTOR";
+
+ /**
* Activity Action: Creates a shortcut.
* <p>Input: Nothing.</p>
* <p>Output: An Intent representing the shortcut. The intent must contain three
@@ -3995,7 +4009,19 @@
* Activity.finishAndRemoveTask()}.
*/
public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000;
-
+ /**
+ * If set, this intent will always match start up as a floating window
+ * in multi window scenarios.
+ * @hide
+ */
+ public static final int FLAG_FLOATING_WINDOW = 0x00003000;
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will cause a newly launching task to be resized according to the split
+ * view metrics, making it running alongside another app.
+ * @hide
+ */
+ public static final int FLAG_ACTIVITY_SPLIT_VIEW = 0x00001000;
/**
* If set, when sending a broadcast only registered receivers will be
* called -- no BroadcastReceiver components will be launched.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5e441a5..65173e5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6143,7 +6143,6 @@
UI_NIGHT_MODE,
SLEEP_TIMEOUT,
PRIVACY_GUARD_DEFAULT,
- ADVANCED_REBOOT,
DEVELOPMENT_SHORTCUT
};
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index b84c3aa..19c66b2 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -135,8 +135,9 @@
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingMult, float spacingAdd) {
- if (width < 0)
- throw new IllegalArgumentException("Layout: " + width + " < 0");
+ if (width < 0) {
+ width = 0;
+ }
// Ensure paint doesn't have baselineShift set.
// While normally we don't modify the paint the user passed in,
@@ -165,7 +166,7 @@
int width, Alignment align,
float spacingmult, float spacingadd) {
if (width < 0) {
- throw new IllegalArgumentException("Layout: " + width + " < 0");
+ width = 0;
}
mText = text;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6877665..6dd5633 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -279,4 +279,10 @@
* @return The frame statistics or null if the window does not exist.
*/
WindowContentFrameStats getWindowContentFrameStats(IBinder token);
+
+ /** FLOAT VIEW **/
+ Rect getAppFullscreenViewRect();
+ Rect getAppMinimumViewRect();
+ Rect getFloatViewRect();
+ void notifyFloatActivityTouched(IBinder token, boolean force);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4fdbe29..1ca6b3a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19876,7 +19876,9 @@
/** @hide */
public void hackTurnOffWindowResizeAnim(boolean off) {
- mAttachInfo.mTurnOffWindowResizeAnim = off;
+ if (mAttachInfo != null) {
+ mAttachInfo.mTurnOffWindowResizeAnim = off;
+ }
}
/**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 47c7202..c5d4323 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -202,6 +202,8 @@
private boolean mDestroyed;
+ public boolean mIsFloatingWindow = false;
+
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
diff --git a/core/java/com/android/internal/util/paranoid/ColorUtils.java b/core/java/com/android/internal/util/paranoid/ColorUtils.java
new file mode 100644
index 0000000..d2c3b0e
--- /dev/null
+++ b/core/java/com/android/internal/util/paranoid/ColorUtils.java
@@ -0,0 +1,404 @@
+/*
+* Copyright (C) 2014 The OmniROM Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.internal.util.paranoid;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.GradientDrawable.Orientation;
+import android.renderscript.Allocation;
+import android.renderscript.Allocation.MipmapControl;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+
+public class ColorUtils {
+
+ public static Drawable getGradientDrawable(boolean isNav, int color) {
+ int color2 = Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
+ GradientDrawable drawable = new GradientDrawable(
+ (isNav ? Orientation.BOTTOM_TOP : Orientation.TOP_BOTTOM),
+ new int[]{color, color2});
+ if (isBrightColor(color)) {
+ color = isNav ? Color.BLACK : Color.WHITE;
+ } else {
+ color = isNav ? Color.WHITE : Color.BLACK;
+ }
+ drawable.setDither(true);
+ drawable.setStroke(1, color);
+ return drawable;
+ }
+
+ public static int darken(final int color, float fraction) {
+ return blendColors(Color.BLACK, color, fraction);
+ }
+
+ public static int lighten(final int color, float fraction) {
+ return blendColors(Color.WHITE, color, fraction);
+ }
+
+ public static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRatio = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRatio);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRatio);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRatio);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ public static int opposeColor(int ColorToInvert) {
+ if (ColorToInvert == -3) {
+ return ColorToInvert;
+ }
+ int RGBMAX = 255;
+ float[] hsv = new float[3];
+ float H;
+ Color.RGBToHSV(Color.red(ColorToInvert),
+ RGBMAX - Color.green(ColorToInvert),
+ Color.blue(ColorToInvert), hsv);
+ H = (float) (hsv[0] + 0.5);
+ if (H > 1) H -= 1;
+ return Color.HSVToColor(hsv);
+ }
+
+ public static int changeColorTransparency(int colorToChange, int reduce) {
+ if (colorToChange == -3) {
+ return colorToChange;
+ }
+ int nots = 255 / 100;
+ int red = Color.red(colorToChange);
+ int blue = Color.blue(colorToChange);
+ int green = Color.green(colorToChange);
+ int alpha = nots * reduce;
+ return Color.argb(alpha, red, green, blue);
+ }
+
+ public static boolean isColorTransparency(int color) {
+ if (color == -3) {
+ return false;
+ }
+ int nots = 255 / 100;
+ int alpha = Color.alpha(color) / nots;
+ return (alpha < 100);
+ }
+
+ public static boolean isBrightColor(int color) {
+ if (color == -3) {
+ return false;
+ } else if (color == Color.TRANSPARENT) {
+ return false;
+ } else if (color == Color.WHITE) {
+ return true;
+ }
+ int[] rgb = { Color.red(color), Color.green(color), Color.blue(color) };
+ int brightness = (int) Math.sqrt(rgb[0] * rgb[0] * .241 + rgb[1]
+ * rgb[1] * .691 + rgb[2] * rgb[2] * .068);
+ if (brightness >= 170) {
+ return true;
+ }
+ return false;
+ }
+
+ public static int getMainColorFromBitmap(Bitmap bitmap, int x, int y) {
+ if (bitmap == null) {
+ return -3;
+ }
+ int pixel = bitmap.getPixel(x, y);
+ int red = Color.red(pixel);
+ int blue = Color.blue(pixel);
+ int green = Color.green(pixel);
+ int alpha = Color.alpha(pixel);
+ return Color.argb(alpha, red, green, blue);
+ }
+
+ public static boolean getIconWhiteBlackTransparent(Drawable drawable) {
+ int color = getDominantColor(drawable);
+ if (color == Color.WHITE) {
+ return true;
+ } else if (color == Color.BLACK) {
+ return true;
+ } else if (color == Color.TRANSPARENT) {
+ return true;
+ } else if (color == -3) {
+ return true;
+ }
+ return false;
+ }
+
+ public static int getIconColorFromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ return -3;
+ }
+ if (drawable.getConstantState() == null) {
+ return -3;
+ }
+ Drawable copyDrawable = drawable.getConstantState().newDrawable();
+ if (copyDrawable == null) {
+ return -3;
+ }
+ if (copyDrawable instanceof ColorDrawable) {
+ return ((ColorDrawable) drawable).getColor();
+ }
+ Bitmap bitmap = drawableToBitmap(copyDrawable);
+ if (bitmap == null) {
+ return -3;
+ }
+ return getDominantColor(bitmap);
+ }
+
+ public static int getAverageColor(Drawable image) {
+ int hSamples = 20;
+ int vSamples = 20;
+ int sampleSize = hSamples * vSamples;
+ float[] sampleTotals = {0, 0, 0};
+ float minimumSaturation = 0.1f;
+ int minimumAlpha = 200;
+ Bitmap b = drawableToBitmap(image);
+ if (b == null) {
+ return -3;
+ }
+ int width = b.getWidth();
+ int height = b.getHeight();
+ float[] hsv = new float[3];
+ int sample;
+ for (int i = 0; i < width; i += (width / hSamples)) {
+ for (int j = 0; j < height; j += (height / vSamples)) {
+ sample = b.getPixel(i, j);
+ Color.colorToHSV(sample, hsv);
+ if ((Color.alpha(sample) > minimumAlpha) && (hsv[1] >= minimumSaturation)) {
+ sampleTotals[0] += hsv[0];
+ sampleTotals[1] += hsv[1];
+ sampleTotals[2] += hsv[2];
+ }
+ }
+ }
+
+ float[] average = new float[3];
+ average[0] = sampleTotals[0] / sampleSize;
+ average[1] = sampleTotals[1] / sampleSize;
+ average[2] = 0.8f;
+
+ return Color.HSVToColor(average);
+ }
+
+ public static int getDominantExampleColor(Bitmap bitmap) {
+ if (bitmap == null) {
+ return -3;
+ }
+
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ int size = width * height;
+ int pixels[] = new int[size];
+
+ Bitmap bitmap2 = bitmap.copy(Config.ARGB_4444, false);
+
+ bitmap2.getPixels(pixels, 0, width, 0, 0, width, height);
+
+ final List<HashMap<Integer, Integer>> colorMap = new ArrayList<HashMap<Integer, Integer>>();
+ colorMap.add(new HashMap<Integer, Integer>());
+ colorMap.add(new HashMap<Integer, Integer>());
+ colorMap.add(new HashMap<Integer, Integer>());
+
+ int color = 0;
+ int r = 0;
+ int g = 0;
+ int b = 0;
+ Integer rC, gC, bC;
+ for (int i = 0; i < pixels.length; i++) {
+ color = pixels[i];
+
+ r = Color.red(color);
+ g = Color.green(color);
+ b = Color.blue(color);
+
+ rC = colorMap.get(0).get(r);
+ if (rC == null) {
+ rC = 0;
+ }
+ colorMap.get(0).put(r, rC++);
+
+ gC = colorMap.get(1).get(g);
+ if (gC == null) {
+ gC = 0;
+ }
+ colorMap.get(1).put(g, gC++);
+
+ bC = colorMap.get(2).get(b);
+ if (bC == null) {
+ bC = 0;
+ }
+ colorMap.get(2).put(b, bC++);
+ }
+
+ int[] rgb = new int[3];
+ for (int i = 0; i < 3; i++) {
+ int max = 0;
+ int val = 0;
+ for (Map.Entry<Integer, Integer> entry : colorMap.get(i).entrySet()) {
+ if (entry.getValue() > max) {
+ max = entry.getValue();
+ val = entry.getKey();
+ }
+ }
+ rgb[i] = val;
+ }
+
+ int dominantColor = Color.rgb(rgb[0], rgb[1], rgb[2]);
+ return dominantColor;
+ }
+
+ public static int getDominantColor(Drawable drawable) {
+ if (drawable == null) {
+ return -3;
+ }
+ Bitmap bitmap = drawableToBitmap(drawable);
+ if (bitmap == null) {
+ return -3;
+ }
+ return getDominantColor(bitmap, true);
+ }
+
+ public static int getDominantColor(Bitmap source) {
+ return getDominantColor(source, true);
+ }
+
+ public static int getDominantColor(Bitmap source, boolean applyThreshold) {
+ if (source == null) {
+ return -3;
+ }
+ int[] colorBins = new int[36];
+ int maxBin = -1;
+ float[] sumHue = new float[36];
+ float[] sumSat = new float[36];
+ float[] sumVal = new float[36];
+ float[] hsv = new float[3];
+
+ int height = source.getHeight();
+ int width = source.getWidth();
+ int[] pixels = new int[width * height];
+ source.getPixels(pixels, 0, width, 0, 0, width, height);
+ for (int row = 0; row < height; row++) {
+ for (int col = 0; col < width; col++) {
+ int c = pixels[col + row * width];
+ if (Color.alpha(c) < 128) {
+ continue;
+ }
+ Color.colorToHSV(c, hsv);
+
+ if (applyThreshold && (hsv[1] <= 0.35f || hsv[2] <= 0.35f)) {
+ continue;
+ }
+
+ int bin = (int) Math.floor(hsv[0] / 10.0f);
+ sumHue[bin] = sumHue[bin] + hsv[0];
+ sumSat[bin] = sumSat[bin] + hsv[1];
+ sumVal[bin] = sumVal[bin] + hsv[2];
+ colorBins[bin]++;
+ if (maxBin < 0 || colorBins[bin] > colorBins[maxBin]) {
+ maxBin = bin;
+ }
+ }
+ }
+
+ if (maxBin < 0) {
+ return -3;
+ }
+ hsv[0] = sumHue[maxBin]/colorBins[maxBin];
+ hsv[1] = sumSat[maxBin]/colorBins[maxBin];
+ hsv[2] = sumVal[maxBin]/colorBins[maxBin];
+ return Color.HSVToColor(hsv);
+ }
+
+ public static int getMainColorFromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ return -3;
+ }
+ if (drawable.getConstantState() == null) {
+ return -3;
+ }
+ Drawable copyDrawable = drawable.getConstantState().newDrawable();
+ if (copyDrawable == null) {
+ return -3;
+ }
+ if (copyDrawable instanceof ColorDrawable) {
+ return ((ColorDrawable) drawable).getColor();
+ }
+ Bitmap bitmap = drawableToBitmap(copyDrawable);
+ if (bitmap == null) {
+ return -3;
+ }
+ if (bitmap.getHeight() > 5) {
+ int pixel = bitmap.getPixel(0, 5);
+ int red = Color.red(pixel);
+ int blue = Color.blue(pixel);
+ int green = Color.green(pixel);
+ int alpha = Color.alpha(pixel);
+ return Color.argb(alpha, red, green, blue);
+ }
+ return -3;
+ }
+
+ public static Bitmap drawableToBitmap(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ Bitmap bitmap = null;
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ if (width > 0 && height > 0) {
+ bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ }
+ return bitmap;
+ }
+
+ public static Bitmap blurBitmap(Context context, Bitmap bmp, int radius) {
+ Bitmap out = Bitmap.createBitmap(bmp);
+ RenderScript rs = RenderScript.create(context);
+
+ Allocation input = Allocation.createFromBitmap(
+ rs, bmp, MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ Allocation output = Allocation.createTyped(rs, input.getType());
+
+ ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+ script.setInput(input);
+ script.setRadius(radius);
+ script.forEach(output);
+
+ output.copyTo(out);
+
+ rs.destroy();
+ return out;
+ }
+}
diff --git a/core/java/com/android/internal/widget/FloatingWindowView.java b/core/java/com/android/internal/widget/FloatingWindowView.java
new file mode 100644
index 0000000..79a0a8d
--- /dev/null
+++ b/core/java/com/android/internal/widget/FloatingWindowView.java
@@ -0,0 +1,273 @@
+package com.android.internal.widget;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+@SuppressLint("ViewConstructor")
+/**
+ * @hide
+ */
+public class FloatingWindowView extends RelativeLayout {
+
+ private static final int ID_OVERLAY_VIEW = 1000000;
+
+ private final int SNAP_LEFT = 1;
+ private final int SNAP_TOP = 2;
+ private final int SNAP_RIGHT = 3;
+ private final int SNAP_BOTTOM = 4;
+
+ private Resources mResource;
+ private RelativeLayout mTitleBarHeader;
+ private ImageButton mTitleBarMin;
+ private ImageButton mTitleBarMax;
+ private TextView mAppLabel;
+ private ImageButton mTitleBarClose;
+ private ImageButton mTitleBarMore;
+ private View mContentViews;
+ private View mDividerViews;
+
+ public FloatingWindowView(final Activity activity, int height) {
+ super(activity);
+ mResource = activity.getResources();
+
+ XmlResourceParser parser = mResource.getLayout(R.layout.floating_window_layout);
+ activity.getLayoutInflater().inflate(parser, this);
+
+ setId(ID_OVERLAY_VIEW);
+ setIsRootNamespace(false);
+
+ mContentViews = findViewById(R.id.floating_window_background);
+ mContentViews.bringToFront();
+
+ setIsRootNamespace(true);
+
+ final FrameLayout decorView =
+ (FrameLayout) activity.getWindow().peekDecorView().getRootView();
+
+ View child = decorView.getChildAt(0);
+ FrameLayout.LayoutParams params =
+ (FrameLayout.LayoutParams) child.getLayoutParams();
+ params.setMargins(0, height, 0, 0);
+ child.setLayoutParams(params);
+
+ mTitleBarHeader = (RelativeLayout) findViewByIdHelper(this, R.id.floating_window_titlebar,
+ "floating_window_titlebar");
+ mTitleBarMore = (ImageButton) findViewByIdHelper(mTitleBarHeader, R.id.floating_window_more,
+ "floating_window_more");
+ mTitleBarClose = (ImageButton) findViewByIdHelper(mTitleBarHeader, R.id.floating_window_close,
+ "floating_window_close");
+ mTitleBarMin = (ImageButton) findViewByIdHelper(mTitleBarHeader, R.id.floating_window_min,
+ "floating_window_min");
+ mTitleBarMax = (ImageButton) findViewByIdHelper(mTitleBarHeader, R.id.floating_window_max,
+ "floating_window_max");
+ mAppLabel = (TextView) findViewByIdHelper(mTitleBarHeader, R.id.floating_window_label,
+ "floating_window_label");
+ mDividerViews = findViewByIdHelper(mTitleBarHeader, R.id.floating_window_line,
+ "floating_window_line");
+
+ if (mTitleBarHeader == null
+ || mTitleBarClose == null
+ || mTitleBarMore == null
+ || mTitleBarMin == null
+ || mTitleBarMax == null
+ || mAppLabel == null
+ || mDividerViews == null) {
+ return;
+ }
+
+ mTitleBarClose.setImageDrawable(mResource.getDrawable(R.drawable.ic_floating_window_close));
+ mTitleBarClose.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ activity.finish();
+ }
+ });
+
+ mTitleBarMin.setImageDrawable(mResource.getDrawable(R.drawable.ic_floating_window_min));
+ mTitleBarMin.setVisibility(View.GONE);
+ mTitleBarMin.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ activity.restorePreviousLayoutApp();
+ mTitleBarMin.setVisibility(View.GONE);
+ mTitleBarMax.setVisibility(View.VISIBLE);
+ }
+ });
+
+ mTitleBarMax.setImageDrawable(mResource.getDrawable(R.drawable.ic_floating_window_max));
+ mTitleBarMax.setVisibility(View.VISIBLE);
+ mTitleBarMax.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ activity.setFullscreenApp();
+ mTitleBarMin.setVisibility(View.VISIBLE);
+ mTitleBarMax.setVisibility(View.GONE);
+ }
+ });
+
+ mAppLabel.setText(activity.getApplicationInfo().loadLabel(activity.getPackageManager()));
+
+ mTitleBarMore.setImageDrawable(mResource.getDrawable(R.drawable.ic_floating_window_more));
+
+ final String menu_item1 = mResource.getString(R.string.floating_window_snap_top);
+ final String menu_item2 = mResource.getString(R.string.floating_window_snap_bottom);
+ final String menu_item3 = mResource.getString(R.string.floating_window_snap_left);
+ final String menu_item4 = mResource.getString(R.string.floating_window_snap_right);
+ final String menu_item5 = mResource.getString(R.string.floating_window_minimize);
+
+ final PopupMenu popupMenu = new PopupMenu(mTitleBarMore.getContext(), mTitleBarMore);
+ Menu menu = popupMenu.getMenu();
+ menu.add(menu_item1);
+ menu.add(menu_item2);
+ menu.add(menu_item3);
+ menu.add(menu_item4);
+ menu.add(menu_item5);
+
+ mTitleBarMore.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (item.getTitle().equals(menu_item1)) {
+ activity.forceSnap(SNAP_TOP);
+ } else if (item.getTitle().equals(menu_item2)) {
+ activity.forceSnap(SNAP_BOTTOM);
+ } else if (item.getTitle().equals(menu_item3)) {
+ activity.forceSnap(SNAP_LEFT);
+ } else if (item.getTitle().equals(menu_item4)) {
+ activity.forceSnap(SNAP_RIGHT);
+ } else if (item.getTitle().equals(menu_item5)) {
+ activity.sendAppLaunchBroadcast();
+ }
+ return false;
+ }
+ });
+ popupMenu.show();
+ }
+ });
+
+ RelativeLayout.LayoutParams header_param =
+ (LayoutParams) mTitleBarHeader.getLayoutParams();
+ header_param.height = height;
+ mTitleBarHeader.setLayoutParams(header_param);
+ mTitleBarHeader.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ activity.setTouchViewDown(event.getX(), event.getY());
+ activity.onUserInteraction();
+ activity.updateFocusApp();
+ if (!activity.getChangedPreviousRange()) {
+ activity.setPreviousTouchRange(event.getRawX(), event.getRawY());
+ activity.setChangedPreviousRange(true);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ activity.changeFlagsLayoutParams();
+ activity.setTouchViewMove(event.getRawX(), event.getRawY());
+ if (activity.getRestorePosition()
+ && activity.moveRangeAboveLimit(event)) {
+ activity.restoreOldPosition();
+ }
+ activity.showSnap((int) event.getRawX(), (int) event.getRawY());
+ break;
+ case MotionEvent.ACTION_UP:
+ activity.setChangedFlags(false);
+ activity.finishSnap(activity.isValidSnap()
+ && activity.getTimeoutDone());
+ activity.discardTimeout();
+ activity.setChangedPreviousRange(false);
+ break;
+ }
+ return view.onTouchEvent(event);
+ }
+ });
+
+ ViewGroup.LayoutParams divider_param = mDividerViews.getLayoutParams();
+ divider_param.height = 2;
+ mDividerViews.setLayoutParams(divider_param);
+ }
+
+ private View findViewByIdHelper(View view, int id, String tag) {
+ View v = view.findViewById(id);
+ if (v == null) {
+ v = findViewWithTag(view, tag);
+ }
+ return v;
+ }
+
+ private View findViewWithTag(View view, String text) {
+ if (view.getTag() instanceof String) {
+ if (((String) view.getTag()).equals(text)) {
+ return view;
+ }
+ }
+ if (view instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); ++i) {
+ final View child = group.getChildAt(i);
+ final View found = findViewWithTag(child, text);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ public void setFloatingBackgroundColor(int color) {
+ if (mTitleBarHeader == null
+ || mContentViews == null) {
+ return;
+ }
+ mTitleBarHeader.setBackgroundColor(color);
+ }
+
+ public void setFloatingColorFilter(int color) {
+ if (mTitleBarClose == null
+ || mTitleBarMin == null
+ || mTitleBarMax == null
+ || mTitleBarMore == null
+ || mDividerViews == null) {
+ return;
+ }
+ mTitleBarMore.setColorFilter(color, Mode.SRC_ATOP);
+ mTitleBarClose.setColorFilter(color, Mode.SRC_ATOP);
+ mTitleBarMin.setColorFilter(color, Mode.SRC_ATOP);
+ mTitleBarMax.setColorFilter(color, Mode.SRC_ATOP);
+ mAppLabel.setTextColor(color);
+ mDividerViews.setBackgroundColor(color);
+ }
+
+ private ShapeDrawable makeOutline(int color, int thickness) {
+ ShapeDrawable rectShapeDrawable = new ShapeDrawable(new RectShape());
+ Paint paint = rectShapeDrawable.getPaint();
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(thickness);
+ return rectShapeDrawable;
+ }
+
+ public static final RelativeLayout.LayoutParams getParams() {
+ final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ params.setMargins(0, 0, 0, 0);
+ return params;
+ }
+}
diff --git a/core/res/res/drawable-hdpi/ic_floating_window_close.png b/core/res/res/drawable-hdpi/ic_floating_window_close.png
new file mode 100644
index 0000000..905ae92
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_floating_window_close.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_floating_window_max.png b/core/res/res/drawable-hdpi/ic_floating_window_max.png
new file mode 100644
index 0000000..f4778a7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_floating_window_max.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_floating_window_min.png b/core/res/res/drawable-hdpi/ic_floating_window_min.png
new file mode 100644
index 0000000..b99016c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_floating_window_min.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_floating_window_more.png b/core/res/res/drawable-hdpi/ic_floating_window_more.png
new file mode 100644
index 0000000..94bd31f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_floating_window_more.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_floating_window_close.png b/core/res/res/drawable-mdpi/ic_floating_window_close.png
new file mode 100644
index 0000000..34b50b5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_floating_window_close.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_floating_window_max.png b/core/res/res/drawable-mdpi/ic_floating_window_max.png
new file mode 100644
index 0000000..69067ce
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_floating_window_max.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_floating_window_min.png b/core/res/res/drawable-mdpi/ic_floating_window_min.png
new file mode 100644
index 0000000..ec5a22a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_floating_window_min.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_floating_window_more.png b/core/res/res/drawable-mdpi/ic_floating_window_more.png
new file mode 100644
index 0000000..3c7d1ef
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_floating_window_more.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_floating_window_close.png b/core/res/res/drawable-xhdpi/ic_floating_window_close.png
new file mode 100644
index 0000000..b9d236f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_floating_window_close.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_floating_window_max.png b/core/res/res/drawable-xhdpi/ic_floating_window_max.png
new file mode 100644
index 0000000..d1c9a52
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_floating_window_max.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_floating_window_min.png b/core/res/res/drawable-xhdpi/ic_floating_window_min.png
new file mode 100644
index 0000000..fcadd56
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_floating_window_min.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_floating_window_more.png b/core/res/res/drawable-xhdpi/ic_floating_window_more.png
new file mode 100644
index 0000000..296267d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_floating_window_more.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_floating_window_close.png b/core/res/res/drawable-xxhdpi/ic_floating_window_close.png
new file mode 100644
index 0000000..dec0d2e
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_floating_window_close.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_floating_window_max.png b/core/res/res/drawable-xxhdpi/ic_floating_window_max.png
new file mode 100644
index 0000000..91b5d34
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_floating_window_max.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_floating_window_min.png b/core/res/res/drawable-xxhdpi/ic_floating_window_min.png
new file mode 100644
index 0000000..d52c750
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_floating_window_min.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_floating_window_more.png b/core/res/res/drawable-xxhdpi/ic_floating_window_more.png
new file mode 100644
index 0000000..b20ab8b
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_floating_window_more.png
Binary files differ
diff --git a/core/res/res/layout/floating_window_layout.xml b/core/res/res/layout/floating_window_layout.xml
new file mode 100644
index 0000000..b630b98
--- /dev/null
+++ b/core/res/res/layout/floating_window_layout.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null" >
+
+ <View
+ android:id="@+id/floating_window_background"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:clickable="false"
+ android:focusable="false"
+ android:focusableInTouchMode="false" />
+
+ <RelativeLayout
+ android:id="@+id/floating_window_titlebar"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:background="#f000"
+ android:tag="floating_window_titlebar" >
+
+ <ImageButton
+ android:id="@+id/floating_window_close"
+ style="@android:style/Widget.Holo.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:adjustViewBounds="true"
+ android:paddingLeft="0dp"
+ android:paddingRight="0dp"
+ android:scaleType="fitCenter"
+ android:src="#FFF"
+ android:tag="floating_window_close" />
+
+ <ImageButton
+ android:id="@+id/floating_window_min"
+ style="@android:style/Widget.Holo.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@+id/floating_window_max"
+ android:adjustViewBounds="true"
+ android:paddingLeft="0dp"
+ android:paddingRight="0dp"
+ android:scaleType="fitCenter"
+ android:src="#FFF"
+ android:tag="floating_window_min" />
+
+ <ImageButton
+ android:id="@+id/floating_window_max"
+ style="@android:style/Widget.Holo.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@+id/floating_window_close"
+ android:adjustViewBounds="true"
+ android:paddingLeft="0dp"
+ android:paddingRight="0dp"
+ android:scaleType="fitCenter"
+ android:src="#FFF"
+ android:tag="floating_window_max" />
+
+ <TextView
+ android:id="@+id/floating_window_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@+id/floating_window_control"
+ android:layout_toRightOf="@+id/floating_window_more"
+ android:ellipsize="end"
+ android:focusable="false"
+ android:gravity="center_vertical|center_horizontal"
+ android:maxLines="1"
+ android:tag="floating_window_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="14sp"
+ android:textStyle="bold" />
+
+ <ImageButton
+ android:id="@+id/floating_window_more"
+ style="@android:style/Widget.Holo.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:adjustViewBounds="true"
+ android:paddingLeft="0dp"
+ android:paddingRight="2dp"
+ android:scaleType="fitCenter"
+ android:src="#FFF"
+ android:tag="floating_window_more" />
+
+ <View
+ android:id="@+id/floating_window_line"
+ android:layout_width="wrap_content"
+ android:layout_height="2dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true"
+ android:background="#fff"
+ android:minHeight="5dp"
+ android:tag="floating_window_line" />
+
+ </RelativeLayout>
+
+</RelativeLayout>
diff --git a/core/res/res/values/cm_strings.xml b/core/res/res/values/cm_strings.xml
index 5ec6f3c..84fce2a 100644
--- a/core/res/res/values/cm_strings.xml
+++ b/core/res/res/values/cm_strings.xml
@@ -346,4 +346,12 @@
<!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
<string name="permdesc_resetBatteryStats">Allows an application to reset the current low-level battery usage data.</string>
+
+ <!-- Floating Window -->
+ <string name="floating_window_snap_top">Snap top</string>
+ <string name="floating_window_snap_bottom">Snap bottom</string>
+ <string name="floating_window_snap_left">Snap left</string>
+ <string name="floating_window_snap_right">Snap right</string>
+ <string name="floating_window_minimize">Minimize</string>
+
</resources>
diff --git a/core/res/res/values/cm_symbols.xml b/core/res/res/values/cm_symbols.xml
index 27e759b..b9b45fe 100644
--- a/core/res/res/values/cm_symbols.xml
+++ b/core/res/res/values/cm_symbols.xml
@@ -338,9 +338,30 @@
<!-- Quick Settings tile defaults -->
<java-symbol type="string" name="config_defaultQuickSettingsTiles" />
-
+
<java-symbol type="array" name="config_externalCMServices" />
<!-- Fingerprint default name -->
<java-symbol type="string" name="fingerprint_default_name" />
+
+ <!-- Floating Window -->
+ <java-symbol type="drawable" name="ic_floating_window_close" />
+ <java-symbol type="drawable" name="ic_floating_window_max" />
+ <java-symbol type="drawable" name="ic_floating_window_min" />
+ <java-symbol type="drawable" name="ic_floating_window_more" />
+ <java-symbol type="id" name="floating_window_background" />
+ <java-symbol type="id" name="floating_window_titlebar" />
+ <java-symbol type="id" name="floating_window_close" />
+ <java-symbol type="id" name="floating_window_label" />
+ <java-symbol type="id" name="floating_window_max" />
+ <java-symbol type="id" name="floating_window_min" />
+ <java-symbol type="id" name="floating_window_more" />
+ <java-symbol type="id" name="floating_window_line" />
+ <java-symbol type="layout" name="floating_window_layout" />
+ <java-symbol type="string" name="floating_window_snap_top"/>
+ <java-symbol type="string" name="floating_window_snap_bottom"/>
+ <java-symbol type="string" name="floating_window_snap_left"/>
+ <java-symbol type="string" name="floating_window_snap_right"/>
+ <java-symbol type="string" name="floating_window_minimize"/>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d3d6aa2..c3acd98 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1392,6 +1392,8 @@
<java-symbol type="style" name="Theme.DeviceDefault.Dialog.NoFrame" />
<java-symbol type="style" name="Theme.IconMenu" />
<java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" />
+ <java-symbol type="style" name="Theme.DeviceDefault.FloatingWindow" />
+ <java-symbol type="style" name="Theme.DeviceDefault.FloatingWindowLight" />
<java-symbol type="attr" name="mediaRouteButtonStyle" />
<java-symbol type="attr" name="externalRouteEnabledDrawable" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index def8659..afae2b4 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -555,4 +555,28 @@
<item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
</style>
+ <style name="Theme.DeviceDefault.FloatingWindow">
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:windowActionModeOverlay">true</item>
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ <item name="android:windowFullscreen">false</item>
+ <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustPan</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.FloatingWindowLight" parent="Theme.Material.Light.Dialog">
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:windowActionModeOverlay">true</item>
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ <item name="android:windowFullscreen">false</item>
+ <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustPan</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_floating_on.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_floating_on.png
new file mode 100644
index 0000000..b37f6b2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_floating_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_floating_on.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_floating_on.png
new file mode 100644
index 0000000..b83b21c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_floating_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_floating_on.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_floating_on.png
new file mode 100644
index 0000000..371f88a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_floating_on.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_floating_on.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_floating_on.png
new file mode 100644
index 0000000..964cd3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_floating_on.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index effa302..acbdf15 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -105,5 +105,16 @@
android:contentDescription="@string/status_bar_notification_inspect_item_title"
android:src="@drawable/ic_info"
/>
+
+ <ImageButton style="@android:style/Widget.Material.Light.Button.Borderless.Small"
+ android:id="@+id/notification_float_item"
+ android:layout_width="52dp"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:gravity="center"
+ android:contentDescription="@string/recent_float_mode_title"
+ android:src="@drawable/ic_qs_floating_on"
+ />
+
</LinearLayout>
</com.android.systemui.statusbar.NotificationGuts>
diff --git a/packages/SystemUI/res/menu/recent_popup_menu.xml b/packages/SystemUI/res/menu/recent_popup_menu.xml
index 9b605f9..db42adb 100644
--- a/packages/SystemUI/res/menu/recent_popup_menu.xml
+++ b/packages/SystemUI/res/menu/recent_popup_menu.xml
@@ -23,4 +23,5 @@
<item android:id="@+id/recent_force_stop" android:title="@string/advanced_dev_option_force_stop" />
<item android:id="@+id/recent_wipe_app" android:title="@string/advanced_dev_option_wipe_app" />
<item android:id="@+id/recent_uninstall" android:title="@string/advanced_dev_option_uninstall" />
+ <item android:id="@+id/recent_float_mode" android:title="@string/recent_float_mode_title" />
</menu>
diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml
index 80b4f50..27bd45c 100644
--- a/packages/SystemUI/res/values/cm_strings.xml
+++ b/packages/SystemUI/res/values/cm_strings.xml
@@ -306,4 +306,6 @@
<!-- Content description of the dock battery level icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_dock_battery_level">Dock battery <xliff:g id="number">%d</xliff:g> percent.</string>
+ <string name="recent_float_mode_title">Floating mode</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 33f6564..bb70ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -45,6 +45,10 @@
public static final int X = 0;
public static final int Y = 1;
+ public static final int LEFT = 0;
+ public static final int RIGHT = 1;
+ public static final int UP = 2;
+ public static final int DOWN = 3;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private final Interpolator mFastOutLinearInInterpolator;
@@ -66,7 +70,12 @@
private Callback mCallback;
private Handler mHandler;
private int mSwipeDirection;
+ private int mSwipeDirectionDetail;
private VelocityTracker mVelocityTracker;
+
+ private boolean mTriggerEnabled = false;
+ private int mTriggerDirection;
+ private boolean mTriggerChild;
private float mInitialTouchPos;
private boolean mDragging;
@@ -109,6 +118,14 @@
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ public void setTriggerEnabled(boolean triggerEnabled) {
+ mTriggerEnabled = triggerEnabled;
+ }
+
+ public void setTriggerDirection(int triggerDirection) {
+ mTriggerDirection = triggerDirection;
}
private float getPos(MotionEvent ev) {
@@ -183,6 +200,11 @@
}
}
invalidateGlobalRegion(animView);
+ }
+
+ private boolean canChildBeDismissed(View view) {
+ return mCallback.canChildBeDismissed(view) &&
+ !(mTriggerEnabled && mSwipeDirectionDetail == mTriggerDirection);
}
// invalidate the view's own bounds all the way up the view hierarchy
@@ -368,7 +390,7 @@
public void snapChild(final View view, float velocity) {
final View animView = mCallback.getChildContentView(view);
- final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
+ final boolean canAnimViewBeDismissed = canChildBeDismissed(animView);
ObjectAnimator anim = createTranslationAnimation(animView, 0);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
@@ -381,6 +403,9 @@
public void onAnimationEnd(Animator animator) {
updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
mCallback.onChildSnappedBack(animView);
+ if (mTriggerChild) {
+ mCallback.onChildTriggered(view);
+ }
}
});
anim.start();
@@ -414,13 +439,18 @@
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
float delta = getPos(ev) - mInitialTouchPos;
+
+ mSwipeDirectionDetail = delta < 0 ?
+ (mSwipeDirection == X ? LEFT : UP) :
+ (mSwipeDirection == X ? RIGHT : DOWN);
+
float absDelta = Math.abs(delta);
if (absDelta >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
}
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
- if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
+ if (CONSTRAIN_SWIPE && !canChildBeDismissed(mCurrView)) {
float size = getSize(mCurrAnimView);
float maxScrollDistance = 0.15f * size;
if (absDelta >= size) {
@@ -431,7 +461,7 @@
}
setTranslation(mCurrAnimView, delta);
- updateSwipeProgressFromOffset(mCurrAnimView, mCanCurrViewBeDimissed);
+ updateSwipeProgressFromOffset(mCurrAnimView, canChildBeDismissed(mCurrView));
}
break;
case MotionEvent.ACTION_UP:
@@ -452,9 +482,13 @@
boolean falsingDetected = mCallback.isAntiFalsingNeeded()
&& !mTouchAboveFalsingThreshold;
- boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+ boolean dismissChild = canChildBeDismissed(mCurrView)
&& !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough)
&& ev.getActionMasked() == MotionEvent.ACTION_UP;
+
+ mTriggerChild = mTriggerEnabled &&
+ mSwipeDirectionDetail == mTriggerDirection &&
+ (childSwipedFastEnough || childSwipedFarEnough);
if (dismissChild) {
// flingadingy
@@ -488,6 +522,8 @@
void onChildDismissed(View v);
+ void onChildTriggered(View v);
+
void onDragCancelled(View v);
void onChildSnappedBack(View animView);
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 330333a..35585c9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -216,6 +216,9 @@
contentView.setTranslationY(0);
}
+ public void onChildTriggered(View v) {
+ }
+
public void onBeginDrag(View v) {
// We do this so the underlying ScrollView knows that it won't get
// the chance to intercept events anymore
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index 1e247be..7d6a358 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -224,6 +224,9 @@
contentView.setTranslationX(0);
}
+ public void onChildTriggered(View v) {
+ }
+
public void onBeginDrag(View v) {
// We do this so the underlying ScrollView knows that it won't get
// the chance to intercept events anymore
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 32d3cdc..53d9ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -630,6 +630,18 @@
}
@Override
+ public void onTaskFloatClicked(Task t) {
+ Intent baseIntent = t.key.baseIntent;
+ // Hide and go home
+ onRecentsHidden();
+ mCb.onTaskLaunchFailed();
+ // Launch task in floating mode
+ baseIntent.setFlags(Intent.FLAG_FLOATING_WINDOW
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(baseIntent);
+ }
+
+ @Override
public void onTaskViewDismissed(Task t) {
// Remove any stored data from the loader. We currently don't bother notifying the views
// that the data has been unloaded because at the point we call onTaskViewDismissed(), the views
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index be5ed77..953421d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -37,7 +37,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.PopupMenu;
-
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
@@ -65,6 +64,7 @@
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
boolean lockToTask);
public void onTaskViewAppInfoClicked(Task t);
+ public void onTaskFloatClicked(Task t);
public void onTaskViewDismissed(Task t);
public void onAllTaskViewsDismissed();
public void onTaskStackFilterTriggered();
@@ -1132,6 +1132,13 @@
}
@Override
+ public void onTaskFloatClicked(TaskView tv) {
+ if (mCb != null) {
+ mCb.onTaskFloatClicked(tv.getTask());
+ }
+ }
+
+ @Override
public void onTaskViewLongClicked(final TaskView tv) {
final PopupMenu popup = new PopupMenu(getContext(), tv.mHeaderView.mApplicationIcon);
mPopup = popup;
@@ -1166,6 +1173,9 @@
case R.id.recent_inspect_item:
onTaskViewAppInfoClicked(tv);
break;
+ case R.id.recent_float_mode:
+ onTaskFloatClicked(tv);
+ break;
case R.id.recent_force_stop:
{
ActivityManager am = (ActivityManager) getContext()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index ab2f57d..ef48842 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -42,6 +42,7 @@
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
public void onTaskViewAppInfoClicked(TaskView tv);
+ public void onTaskFloatClicked(TaskView tv);
public void onTaskViewLongClicked(TaskView tv);
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d232c2e..5f439b7 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -826,6 +826,41 @@
startNotificationGutsIntent(intent, appUid);
}
+ private void launchFloating(PendingIntent pIntent) {
+ Intent overlay = new Intent();
+ overlay.addFlags(Intent.FLAG_FLOATING_WINDOW | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ try {
+ pIntent.send(mContext, 0, overlay);
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here. Just log the exception message.
+ Slog.w(TAG, "Sending contentIntent failed: " + e);
+ }
+ final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
+ dismissKeyguardThenExecute(new OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ AsyncTask.execute(new Runnable() {
+ public void run() {
+ try {
+ if (keyguardShowing) {
+ ActivityManagerNative.getDefault()
+ .keyguardWaitingForActivityDrawn();
+ }
+ overrideActivityPendingAppTransition(keyguardShowing);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ return true;
+ }
+ }, false /* afterKeyguardGone */);
+ }
+
private void startNotificationGutsIntent(final Intent intent, final int appUid) {
final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
dismissKeyguardThenExecute(new OnDismissAction() {
@@ -864,6 +899,7 @@
row.setTag(sbn.getPackageName());
final View guts = row.findViewById(R.id.notification_guts);
final String pkg = sbn.getPackageName();
+ final PendingIntent contentIntent = sbn.getNotification().contentIntent;
String appname = pkg;
Drawable pkgicon = null;
int appUid = -1;
@@ -883,6 +919,7 @@
((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon);
((DateTimeView) row.findViewById(R.id.timestamp)).setTime(sbn.getPostTime());
((TextView) row.findViewById(R.id.pkgname)).setText(appname);
+ final View floatButton = guts.findViewById(R.id.notification_float_item);
final View settingsButton = guts.findViewById(R.id.notification_inspect_item);
final View appSettingsButton
= guts.findViewById(R.id.notification_inspect_app_provided_settings);
@@ -906,6 +943,13 @@
mContext.getContentResolver().insert(SPAM_MESSAGE_URI, values);
removeNotification(sbn.getKey(), null);
onNotificationClear(sbn);
+ }
+ });
+
+ floatButton.setVisibility(View.VISIBLE);
+ floatButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ launchFloating(contentIntent);
}
});
@@ -934,8 +978,9 @@
} else {
appSettingsButton.setVisibility(View.GONE);
}
- } else {
- settingsButton.setVisibility(View.GONE);
+ }
+ if (appUid < 0) {
+ floatButton.setVisibility(View.GONE);
appSettingsButton.setVisibility(View.GONE);
filterButton.setVisibility(View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index d45c68b..303d66c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -397,6 +397,10 @@
}
@Override
+ public void onChildTriggered(View v) {
+ }
+
+ @Override
public void onBeginDrag(View v) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 7939121..fc91d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -578,6 +578,9 @@
return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
}
+ public void onChildTriggered(View v) {
+ }
+
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index c203c75..77e271b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -39,6 +39,7 @@
import com.android.internal.widget.DecorContentParent;
import com.android.internal.widget.SwipeDismissLayout;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -2583,8 +2584,17 @@
return false;
}
final Callback cb = getCallback();
- return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
- : super.dispatchTouchEvent(ev);
+ if (cb != null && !isDestroyed() && mFeatureId < 0) {
+ if (cb instanceof android.app.Activity && mIsFloatingWindow) {
+ android.app.Activity act = (android.app.Activity)cb;
+ if (shouldCloseOnTouch(act, ev)) {
+ act.finishFloating();
+ return true;
+ }
+ }
+ return cb.dispatchTouchEvent(ev);
+ }
+ return super.dispatchTouchEvent(ev);
}
@Override
@@ -3434,7 +3444,7 @@
void updateWindowResizeState() {
Drawable bg = getBackground();
hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity()
- != PixelFormat.OPAQUE);
+ != PixelFormat.OPAQUE || mIsFloatingWindow);
}
@Override
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index d97aec7..aae09dd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4242,6 +4242,10 @@
}
}
+ public Rect getContentRect() {
+ return new Rect(mContentLeft, mContentTop, mContentRight, mContentBottom);
+ }
+
/** {@inheritDoc} */
@Override
public void layoutWindowLw(WindowState win, WindowState attached) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd2a7af..ae33233 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8978,6 +8978,23 @@
}
}
+ public IBinder getActivityForTask(int task, boolean onlyRoot) {
+ final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
+ synchronized(this) {
+ ArrayList<ActivityStack> stacks = mStackSupervisor.getStacks();
+ for (ActivityStack stack : stacks) {
+ TaskRecord r = stack.taskForIdLocked(task);
+
+ if (r != null && r.getTopActivity() != null) {
+ return r.getTopActivity().appToken;
+ } else {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
private boolean isLockTaskAuthorized(String pkg) {
final DevicePolicyManager dpm = (DevicePolicyManager)
mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e10acdf..e1800cf 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -34,6 +34,7 @@
import android.app.ActivityOptions;
import android.app.ResultInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -54,6 +55,7 @@
import android.util.Slog;
import android.util.TimeUtils;
import android.view.IApplicationToken;
+import android.view.ContextThemeWrapper;
import android.view.WindowManager;
import org.xmlpull.v1.XmlPullParser;
@@ -166,6 +168,10 @@
long lastLaunchTime; // time of last lauch of this activity
ArrayList<ActivityContainer> mChildContainers = new ArrayList<ActivityContainer>();
+ boolean topIntent;
+ boolean newAppTask;
+ boolean floatingWindow;
+
String stringName; // for caching of toString().
private boolean inHistory; // are we in the history stack?
@@ -423,7 +429,9 @@
// is that we have everything, and we shouldn't never consider it
// lacking in state to be removed if it dies.
haveState = true;
-
+ topIntent = false;
+ floatingWindow = false;
+
if (aInfo != null) {
if (aInfo.targetActivity == null
|| aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE
@@ -454,6 +462,44 @@
? android.R.style.Theme
: android.R.style.Theme_Holo;
}
+ // This is where the package gets its first context from the attribute-cache
+ // In order to hook its attributes we set up our check for floating multi windows here.
+ topIntent = true;
+ ActivityStack stack = supervisor.getFocusedStack();
+ floatingWindow = (intent.getFlags() & Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW;
+ TaskRecord baseRecord = stack != null && stack.mTaskHistory.size() > 0 ? stack.mTaskHistory.get(stack.mTaskHistory.size() -1) : null;
+
+ if (baseRecord != null) {
+ ActivityRecord record = baseRecord.mActivities.size() > 0 ? baseRecord.mActivities.get(baseRecord.mActivities.size() - 1) : null;
+ final boolean floats = (baseRecord.intent.getFlags() & Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW;
+ final boolean taskAppAffinity = record == null ? false : aInfo.applicationInfo.packageName.equals(record.packageName);
+ newAppTask = (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK;
+ // If the current intent is not a new task we will check its top parent.
+ // Perhaps it started out as a multiwindow in which case we pass the flag on
+ if (floats && (!newAppTask || taskAppAffinity)) {
+ intent.addFlags(Intent.FLAG_FLOATING_WINDOW);
+ // Flag the activity as sub-task
+ topIntent = false;
+ floatingWindow = true;
+ }
+ }
+ // If this is a multiwindow activity we prevent it from messing up the history stack,
+ // like jumping back home, killing the current activity or polluting recents
+ if (floatingWindow) {
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ // If this is the mother-intent we make it volatile
+ if (topIntent) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ }
+ // Change theme
+ realTheme = com.android.internal.R.style.Theme_DeviceDefault_FloatingWindow;
+ } else {
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_FLOATING_WINDOW);
+ }
+
if ((aInfo.flags&ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
@@ -466,8 +512,8 @@
processName = aInfo.processName;
}
- if (intent != null && (aInfo.flags & ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS) != 0) {
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if ((intent != null && (aInfo.flags & ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS) != 0)
+ || floatingWindow) {
}
packageName = aInfo.applicationInfo.packageName;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 48e5b32..70efae6 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -46,6 +46,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.AttributeCache;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
@@ -83,6 +84,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
+import android.view.ContextThemeWrapper;
import android.view.Display;
import java.io.FileDescriptor;
@@ -152,7 +154,7 @@
* The back history of all previous (and possibly still
* running) activities. It contains #TaskRecord objects.
*/
- private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
+ ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
/**
* Used for validating app tokens with window manager.
@@ -1313,13 +1315,13 @@
// Aggregate current change flags.
configChanges |= r.configChangeFlags;
- if (r.fullscreen) {
+ if (r.fullscreen && !r.floatingWindow) {
// At this point, nothing else needs to be shown
if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
behindFullscreen = true;
} else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
- behindFullscreen = true;
+ behindFullscreen = !isHomeStack() && r.frontOfTask && task.isOverHomeStack();
}
} else {
if (DEBUG_VISBILITY) Slog.v(
@@ -1507,6 +1509,7 @@
mStackSupervisor.mUserLeaving = false;
final TaskRecord prevTask = prev != null ? prev.task : null;
+
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
@@ -1536,12 +1539,25 @@
}
final TaskRecord nextTask = next.task;
+ boolean isFloatingWindow = prev != null ? prev.floatingWindow : false;
if (prevTask != null && prevTask.stack == this &&
prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
prevTask.setFrontOfTask();
- } else if (prevTask != topTask()) {
+ } else if (prevTask != topTask() && !isFloatingWindow) {
+ ArrayList<ActivityRecord> activities = prevTask.mActivities;
+ final int numActivities = activities.size();
+ for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ // r is usually the same as next, but what if two activities were launched
+ // before prev finished?
+ if (!r.finishing) {
+ r.frontOfTask = true;
+ break;
+ }
+ }
+ } else if (prevTask != topTask() && !prev.floatingWindow) {
// This task is going away but it was supposed to return to the home stack.
// Now the task above it has to return to the home task instead.
final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
@@ -1637,7 +1653,7 @@
// can be resumed...
boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0;
boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause);
- if (mResumedActivity != null) {
+ if (mResumedActivity != null && !next.floatingWindow) {
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity);
pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);
}
@@ -1718,7 +1734,7 @@
if (prev.finishing) {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: prev=" + prev);
- if (mNoAnimActivities.contains(prev)) {
+ if (mNoAnimActivities.contains(prev) || next.floatingWindow) {
anim = false;
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
} else {
@@ -1733,7 +1749,7 @@
mWindowManager.setAppVisibility(prev.appToken, false);
} else {
if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev);
- if (mNoAnimActivities.contains(next)) {
+ if (mNoAnimActivities.contains(next) || next.floatingWindow) {
anim = false;
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
} else {
@@ -1876,7 +1892,7 @@
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
} else if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
- mStackSupervisor.isFrontStack(lastStack)) {
+ mStackSupervisor.isFrontStack(lastStack) && !next.floatingWindow) {
mWindowManager.setAppStartingWindow(
next.appToken, next.packageName, next.theme,
mService.compatibilityInfoForPackageLocked(next.info.applicationInfo),
@@ -1909,7 +1925,7 @@
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
} else {
- if (SHOW_APP_STARTING_PREVIEW) {
+ if (SHOW_APP_STARTING_PREVIEW && !next.floatingWindow) {
mWindowManager.setAppStartingWindow(
next.appToken, next.packageName, next.theme,
mService.compatibilityInfoForPackageLocked(
@@ -2090,11 +2106,11 @@
}
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare open transition: starting " + r);
- if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ if (((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) || (r.floatingWindow && !r.topIntent)) {
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);
mNoAnimActivities.add(r);
} else {
- mWindowManager.prepareAppTransition(newTask
+ mWindowManager.prepareAppTransition(newTask && !r.floatingWindow
? r.mLaunchTaskBehind
? AppTransition.TRANSIT_TASK_OPEN_BEHIND
: AppTransition.TRANSIT_TASK_OPEN
@@ -2125,7 +2141,7 @@
// tell WindowManager that r is visible even though it is at the back of the stack.
mWindowManager.setAppVisibility(r.appToken, true);
ensureActivitiesVisibleLocked(null, 0);
- } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
+ } else if (SHOW_APP_STARTING_PREVIEW && doShow && !r.floatingWindow) {
// Figure out if we are transitioning from another activity that is
// "has the same starting icon" as the next one. This allows the
// window manager to keep the previous window it had previously
@@ -2808,7 +2824,7 @@
boolean endTask = index <= 0;
if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: finishing " + r);
- mWindowManager.prepareAppTransition(endTask
+ mWindowManager.prepareAppTransition(endTask && !r.floatingWindow
? AppTransition.TRANSIT_TASK_CLOSE
: AppTransition.TRANSIT_ACTIVITY_CLOSE, false);
@@ -3572,7 +3588,7 @@
if (numTasks == 0 || index < 0) {
// nothing to do!
if (source != null &&
- (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ ((source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0 || source.floatingWindow)) {
ActivityOptions.abort(options);
} else {
updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
@@ -3587,7 +3603,7 @@
if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr);
if (source != null &&
- (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ ((source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION)) != 0 ) {
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d47ec40..d5a0d0c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1907,7 +1907,7 @@
}
options = null;
movedToFront = true;
- }
+ }
}
// If the caller has requested that the target task be
// reset, then do so.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 584f014..13d73aa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -157,6 +157,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
import java.util.List;
/** {@hide} */
@@ -5689,6 +5690,120 @@
mLastStatusBarVisibility |= flags;
}
+ private void moveTaskAndActivityToFront(int taskId) {
+ try {
+ moveTaskToTop(taskId);
+ mActivityManager.moveTaskToFront(taskId, 0, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot move the activity to front", e);
+ }
+ }
+
+ @Override
+ public void notifyFloatActivityTouched(IBinder token, boolean force) {
+ synchronized(mWindowMap) {
+ boolean changed = false;
+ if (token != null) {
+ AppWindowToken newFocus = findAppWindowToken(token);
+ if (newFocus == null) {
+ Slog.w(TAG, "Attempted to set focus to non-existing app token: " + token);
+ return;
+ }
+ changed = mFocusedApp != newFocus;
+ mFocusedApp = newFocus;
+ if (changed || force) {
+ if (DEBUG_FOCUS) Slog.v(TAG, "Changed app focus to " + token);
+ mInputMonitor.setFocusedAppLw(newFocus);
+ }
+ }
+
+ if (changed || force) {
+ final long origId = Binder.clearCallingIdentity();
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true);
+ mH.removeMessages(H.REPORT_FOCUS_CHANGE);
+ mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ if (!force) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ int taskId = mActivityManager.getTaskForActivity(token, false);
+ moveTaskAndActivityToFront(taskId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot move the activity to front", e);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public Rect getAppFullscreenViewRect() {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final boolean rotated = (mRotation == Surface.ROTATION_90
+ || mRotation == Surface.ROTATION_270);
+ final int realdw = rotated ?
+ displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
+ final int realdh = rotated ?
+ displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
+
+ int dw = realdw;
+ int dh = realdh;
+
+ // Get application display metrics.
+ int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
+ int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+
+ return new Rect(0, 0, appWidth, appHeight);
+ }
+
+ @Override
+ public Rect getAppMinimumViewRect() {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final boolean rotated = (mRotation == Surface.ROTATION_90
+ || mRotation == Surface.ROTATION_270);
+ final int realdw = rotated ?
+ displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
+ final int realdh = rotated ?
+ displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
+
+ int dw = realdw;
+ int dh = realdh;
+
+ // Get application display metrics.
+ int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
+ int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+
+ return new Rect(0, 0, (int)(appWidth * 0.5f) , (int)(appHeight * 0.5f));
+ }
+
+ @Override
+ public Rect getFloatViewRect() {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ final boolean rotated = (mRotation == Surface.ROTATION_90
+ || mRotation == Surface.ROTATION_270);
+ final int realdw = rotated ?
+ displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
+ final int realdh = rotated ?
+ displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
+ final boolean nativeLandscape =
+ (displayContent.mBaseDisplayHeight < displayContent.mBaseDisplayWidth);
+
+ int dw = realdw;
+ int dh = realdh;
+
+ // Get application display metrics.
+ int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
+ int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+
+ if (nativeLandscape ^ rotated) {
+ return new Rect(0, 0, (int)(appWidth * 0.7f), (int)(appHeight * 0.9f));
+ } else {
+ return new Rect(0, 0, (int)(appWidth * 0.9f) , (int)(appHeight * 0.7f));
+ }
+ }
+
// Called by window manager policy. Not exposed externally.
@Override
public int getLidState() {
@@ -10843,8 +10958,16 @@
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: Found new focus @ " + i +
" = " + win);
- return win;
- }
+
+ // Dispatch to this window if it is wants key events.
+ if (win.canReceiveKeys()) {
+ if (mFocusedApp != null) {
+ return win;
+ } else {
+ return win;
+ }
+ }
+ }
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: No focusable windows.");
return null;
@@ -11178,7 +11301,7 @@
return mPolicy.hasPermanentMenuKey();
}
- @Override
+ @Override
public boolean needsNavigationBar() {
return mPolicy.needsNavigationBar();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment