Better compat mode part one: start scaling windows.

First step of improving app screen size compatibility mode.  When
running in compat mode, an application's windows are scaled up on
the screen rather than being small with 1:1 pixels.

Currently we scale the application to fill the entire screen, so
don't use an even pixel scaling.  Though this may have some
negative impact on the appearance (it looks okay to me), it has a
big benefit of allowing us to now treat these apps as normal
full-screens apps and do the normal transition animations as you
move in and out and around in them.

This introduces fun stuff in the input system to take care of
modifying pointer coordinates to account for the app window
surface scaling.  The input dispatcher is told about the scale
that is being applied to each window and, when there is one,
adjusts pointer events appropriately as they are being sent
to the transport.

Also modified is CompatibilityInfo, which has been greatly
simplified to not be so insane and incomprehendible.  It is
now simple -- when constructed it determines if the given app
is compatible with the current screen size and density, and
that is that.

There are new APIs on ActivityManagerService to put applications
that we would traditionally consider compatible with larger screens
in compatibility mode.  This is the start of a facility to have
a UI affordance for a user to switch apps in and out of
compatibility.

To test switching of modes, there is a new variation of the "am"
command to do this: am screen-compat [on|off] [package]

This mode switching has the fundamentals of restarting activities
when it is changed, though the state still needs to be persisted
and the overall mode switch cleaned up.

For the few small apps I have tested, things mostly seem to be
working well.  I know of one problem with the text selection
handles being drawn at the wrong position because at some point
the window offset is being scaled incorrectly.  There are
probably other similar issues around the interaction between
two windows because the different window coordinate spaces are
done in a hacky way instead of being formally integrated into
the window manager layout process.

Change-Id: Ie038e3746b448135117bd860859d74e360938557
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index e403ac2..ab9bfce 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -21,9 +21,9 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
@@ -34,32 +34,27 @@
  * 
  *  {@hide} 
  */
-public class CompatibilityInfo {
-    private static final boolean DBG = false;
-    private static final String TAG = "CompatibilityInfo";
-    
+public class CompatibilityInfo implements Parcelable {
     /** default compatibility info object for compatible applications */
     public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
-        @Override
-        public void setExpandable(boolean expandable) {
-            throw new UnsupportedOperationException("trying to change default compatibility info");
-        }
     };
 
     /**
-     * The default width of the screen in portrait mode. 
+     * This is the number of pixels we would like to have along the
+     * short axis of an app that needs to run on a normal size screen.
      */
-    public static final int DEFAULT_PORTRAIT_WIDTH = 320;
+    public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
 
     /**
-     * The default height of the screen in portrait mode. 
-     */    
-    public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
+     * This is the maximum aspect ratio we will allow while keeping
+     * applications in a compatible screen size.
+     */
+    public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
 
     /**
      *  A compatibility flags
      */
-    private int mCompatibilityFlags;
+    private final int mCompatibilityFlags;
     
     /**
      * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
@@ -68,54 +63,27 @@
     private static final int SCALING_REQUIRED = 1; 
 
     /**
-     * A flag mask to indicates that the application can expand over the original size.
-     * The flag is set to true if
-     * 1) Application declares its expandable in manifest file using <supports-screens> or
-     * 2) Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set
-     * {@see compatibilityFlag}
+     * Has the application said that its UI is expandable?  Based on the
+     * <supports-screen> android:expandible in the manifest.
      */
     private static final int EXPANDABLE = 2;
     
     /**
-     * A flag mask to tell if the application is configured to be expandable. This differs
-     * from EXPANDABLE in that the application that is not expandable will be 
-     * marked as expandable if Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set.
-     */
-    private static final int CONFIGURED_EXPANDABLE = 4; 
-
-    /**
-     * A flag mask to indicates that the application supports large screens.
-     * The flag is set to true if
-     * 1) Application declares it supports large screens in manifest file using <supports-screens> or
-     * 2) The screen size is not large
-     * {@see compatibilityFlag}
+     * Has the application said that its UI supports large screens?  Based on the
+     * <supports-screen> android:largeScreens in the manifest.
      */
     private static final int LARGE_SCREENS = 8;
     
     /**
-     * A flag mask to tell if the application supports large screens. This differs
-     * from LARGE_SCREENS in that the application that does not support large
-     * screens will be marked as supporting them if the current screen is not
-     * large.
-     */
-    private static final int CONFIGURED_LARGE_SCREENS = 16; 
-
-    /**
-     * A flag mask to indicates that the application supports xlarge screens.
-     * The flag is set to true if
-     * 1) Application declares it supports xlarge screens in manifest file using <supports-screens> or
-     * 2) The screen size is not xlarge
-     * {@see compatibilityFlag}
+     * Has the application said that its UI supports xlarge screens?  Based on the
+     * <supports-screen> android:xlargeScreens in the manifest.
      */
     private static final int XLARGE_SCREENS = 32;
     
     /**
-     * A flag mask to tell if the application supports xlarge screens. This differs
-     * from XLARGE_SCREENS in that the application that does not support xlarge
-     * screens will be marked as supporting them if the current screen is not
-     * xlarge.
+     * Set if the application needs to run in screen size compatibility mode.
      */
-    private static final int CONFIGURED_XLARGE_SCREENS = 64;
+    private static final int NEEDS_SCREEN_COMPAT = 128;
 
     /**
      * The effective screen density we have selected for this application.
@@ -132,28 +100,55 @@
      */
     public final float applicationInvertedScale;
 
-    /**
-     * The flags from ApplicationInfo.
-     */
-    public final int appFlags;
-    
-    public CompatibilityInfo(ApplicationInfo appInfo) {
-        appFlags = appInfo.flags;
-        
+    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, boolean forceCompat) {
+        int compatFlags = 0;
+
         if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
-            // Saying you support large screens also implies you support xlarge
-            // screens; there is no compatibility mode for a large app on an
-            // xlarge screen.
-            mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS
-                    | XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
-                    | EXPANDABLE | CONFIGURED_EXPANDABLE;
+            compatFlags |= LARGE_SCREENS;
+            if (!forceCompat) {
+                // If we aren't forcing the app into compatibility mode, then
+                // assume if it supports large screens that we should allow it
+                // to use the full space of an xlarge screen as well.
+                compatFlags |= XLARGE_SCREENS | EXPANDABLE;
+            }
         }
         if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
-            mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
-                    | EXPANDABLE | CONFIGURED_EXPANDABLE;
+            compatFlags |= XLARGE_SCREENS | EXPANDABLE;
         }
-        if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
-            mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE;
+        if (!forceCompat) {
+            // If we are forcing compatibility mode, then ignore an app that
+            // just says it is resizable for screens.  We'll only have it fill
+            // the screen if it explicitly says it supports the screen size we
+            // are running in.
+            if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+                compatFlags |= EXPANDABLE;
+            }
+        }
+
+        boolean supportsScreen = false;
+        switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
+            case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+                if ((compatFlags&XLARGE_SCREENS) != 0) {
+                    supportsScreen = true;
+                }
+                break;
+            case Configuration.SCREENLAYOUT_SIZE_LARGE:
+                if ((compatFlags&LARGE_SCREENS) != 0) {
+                    supportsScreen = true;
+                }
+                break;
+        }
+
+        if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) == 0) {
+            if ((compatFlags&EXPANDABLE) != 0) {
+                supportsScreen = true;
+            }
+        }
+
+        if (supportsScreen) {
+            compatFlags &= ~NEEDS_SCREEN_COMPAT;
+        } else {
+            compatFlags |= NEEDS_SCREEN_COMPAT;
         }
         
         if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
@@ -165,13 +160,14 @@
             applicationScale = DisplayMetrics.DENSITY_DEVICE
                     / (float) DisplayMetrics.DENSITY_DEFAULT;
             applicationInvertedScale = 1.0f / applicationScale;
-            mCompatibilityFlags |= SCALING_REQUIRED;
+            compatFlags |= SCALING_REQUIRED;
         }
+
+        mCompatibilityFlags = compatFlags;
     }
 
-    private CompatibilityInfo(int appFlags, int compFlags,
+    private CompatibilityInfo(int compFlags,
             int dens, float scale, float invertedScale) {
-        this.appFlags = appFlags;
         mCompatibilityFlags = compFlags;
         applicationDensity = dens;
         applicationScale = scale;
@@ -179,81 +175,13 @@
     }
 
     private CompatibilityInfo() {
-        this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
-                | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
-                | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
-                | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS
-                | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS,
-                EXPANDABLE | CONFIGURED_EXPANDABLE,
+        this(XLARGE_SCREENS | LARGE_SCREENS | EXPANDABLE,
                 DisplayMetrics.DENSITY_DEVICE,
                 1.0f,
                 1.0f);
     }
 
     /**
-     * Returns the copy of this instance.
-     */
-    public CompatibilityInfo copy() {
-        CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
-                applicationDensity, applicationScale, applicationInvertedScale);
-        return info;
-    }
- 
-    /**
-     * Sets expandable bit in the compatibility flag.
-     */
-    public void setExpandable(boolean expandable) {
-        if (expandable) {
-            mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
-        } else {
-            mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
-        }
-    }
-
-    /**
-     * Sets large screen bit in the compatibility flag.
-     */
-    public void setLargeScreens(boolean expandable) {
-        if (expandable) {
-            mCompatibilityFlags |= CompatibilityInfo.LARGE_SCREENS;
-        } else {
-            mCompatibilityFlags &= ~CompatibilityInfo.LARGE_SCREENS;
-        }
-    }
-
-    /**
-     * Sets large screen bit in the compatibility flag.
-     */
-    public void setXLargeScreens(boolean expandable) {
-        if (expandable) {
-            mCompatibilityFlags |= CompatibilityInfo.XLARGE_SCREENS;
-        } else {
-            mCompatibilityFlags &= ~CompatibilityInfo.XLARGE_SCREENS;
-        }
-    }
-
-    /**
-     * @return true if the application is configured to be expandable.
-     */
-    public boolean isConfiguredExpandable() {
-        return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
-    }
-
-    /**
-     * @return true if the application is configured to be expandable.
-     */
-    public boolean isConfiguredLargeScreens() {
-        return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_LARGE_SCREENS) != 0;
-    }
-
-    /**
-     * @return true if the application is configured to be expandable.
-     */
-    public boolean isConfiguredXLargeScreens() {
-        return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_XLARGE_SCREENS) != 0;
-    }
-
-    /**
      * @return true if the scaling is required
      */
     public boolean isScalingRequired() {
@@ -261,14 +189,12 @@
     }
     
     public boolean supportsScreen() {
-        return (mCompatibilityFlags & (EXPANDABLE|LARGE_SCREENS))
-                == (EXPANDABLE|LARGE_SCREENS);
+        return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
     }
     
     @Override
     public String toString() {
-        return "CompatibilityInfo{scale=" + applicationScale +
-                ", supports screen=" + supportsScreen() + "}";
+        return "CompatibilityInfo{scale=" + applicationScale + "}";
     }
 
     /**
@@ -423,24 +349,144 @@
         }
     }
 
+    public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
+        if (!supportsScreen()) {
+            // This is a larger screen device and the app is not
+            // compatible with large screens, so diddle it.
+            CompatibilityInfo.updateCompatibleScreenFrame(inoutDm, null, inoutDm);
+        }
+
+        if (isScalingRequired()) {
+            float invertedRatio = applicationInvertedScale;
+            inoutDm.density *= invertedRatio;
+            inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
+            inoutDm.scaledDensity *= invertedRatio;
+            inoutDm.xdpi *= invertedRatio;
+            inoutDm.ydpi *= invertedRatio;
+            inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
+            inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+        }
+    }
+
+    public void applyToConfiguration(Configuration inoutConfig) {
+        if (!supportsScreen()) {
+            // This is a larger screen device and the app is not
+            // compatible with large screens, so we are forcing it to
+            // run as if the screen is normal size.
+            inoutConfig.screenLayout =
+                    (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
+                    | Configuration.SCREENLAYOUT_SIZE_NORMAL;
+        }
+    }
+
     /**
-     * Returns the frame Rect for applications runs under compatibility mode.
+     * Compute the frame Rect for applications runs under compatibility mode.
      *
      * @param dm the display metrics used to compute the frame size.
      * @param orientation the orientation of the screen.
      * @param outRect the output parameter which will contain the result.
+     * @return Returns the scaling factor for the window.
      */
-    public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation,
-            Rect outRect) {
-        int width = dm.widthPixels;
-        int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density + 0.5f);
-        int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density + 0.5f);
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            int xOffset = (width - portraitHeight) / 2 ;
-            outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth);
+    public static float updateCompatibleScreenFrame(DisplayMetrics dm,
+            Rect outRect, DisplayMetrics outDm) {
+        final int width = dm.realWidthPixels;
+        final int height = dm.realHeightPixels;
+        int shortSize, longSize;
+        if (width < height) {
+            shortSize = width;
+            longSize = height;
         } else {
-            int xOffset = (width - portraitWidth) / 2 ;
-            outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight);
+            shortSize = height;
+            longSize = width;
         }
+        int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
+        float aspect = ((float)longSize) / shortSize;
+        if (aspect > MAXIMUM_ASPECT_RATIO) {
+            aspect = MAXIMUM_ASPECT_RATIO;
+        }
+        int newLongSize = (int)(newShortSize * aspect + 0.5f);
+        int newWidth, newHeight;
+        if (width < height) {
+            newWidth = newShortSize;
+            newHeight = newLongSize;
+        } else {
+            newWidth = newLongSize;
+            newHeight = newShortSize;
+        }
+
+        float sw = width/(float)newWidth;
+        float sh = height/(float)newHeight;
+        float scale = sw < sh ? sw : sh;
+        if (scale < 1) {
+            scale = 1;
+        }
+
+        if (outRect != null) {
+            final int left = (int)((width-(newWidth*scale))/2);
+            final int top = (int)((height-(newHeight*scale))/2);
+            outRect.set(left, top, left+newWidth, top+newHeight);
+        }
+
+        if (outDm != null) {
+            outDm.widthPixels = newWidth;
+            outDm.heightPixels = newHeight;
+        }
+
+        return scale;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        try {
+            CompatibilityInfo oc = (CompatibilityInfo)o;
+            if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
+            if (applicationDensity != oc.applicationDensity) return false;
+            if (applicationScale != oc.applicationScale) return false;
+            if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+            return true;
+        } catch (ClassCastException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mCompatibilityFlags;
+        result = 31 * result + applicationDensity;
+        result = 31 * result + Float.floatToIntBits(applicationScale);
+        result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+        return result;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mCompatibilityFlags);
+        dest.writeInt(applicationDensity);
+        dest.writeFloat(applicationScale);
+        dest.writeFloat(applicationInvertedScale);
+    }
+
+    public static final Parcelable.Creator<CompatibilityInfo> CREATOR
+            = new Parcelable.Creator<CompatibilityInfo>() {
+        public CompatibilityInfo createFromParcel(Parcel source) {
+            return new CompatibilityInfo(source);
+        }
+
+        public CompatibilityInfo[] newArray(int size) {
+            return new CompatibilityInfo[size];
+        }
+    };
+
+    private CompatibilityInfo(Parcel source) {
+        mCompatibilityFlags = source.readInt();
+        applicationDensity = source.readInt();
+        applicationScale = source.readFloat();
+        applicationInvertedScale = source.readFloat();
     }
 }
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 47c2623..908db11 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -703,11 +703,20 @@
     }
     
     public int hashCode() {
-        return ((int)this.fontScale) + this.mcc + this.mnc
-                + (this.locale != null ? this.locale.hashCode() : 0)
-                + this.touchscreen
-                + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
-                + this.navigation + this.navigationHidden
-                + this.orientation + this.screenLayout + this.uiMode;
+        int result = 17;
+        result = 31 * result + Float.floatToIntBits(fontScale);
+        result = 31 * result + mcc;
+        result = 31 * result + mnc;
+        result = 31 * result + (locale != null ? locale.hashCode() : 0);
+        result = 31 * result + touchscreen;
+        result = 31 * result + keyboard;
+        result = 31 * result + keyboardHidden;
+        result = 31 * result + hardKeyboardHidden;
+        result = 31 * result + navigation;
+        result = 31 * result + navigationHidden;
+        result = 31 * result + orientation;
+        result = 31 * result + screenLayout;
+        result = 31 * result + uiMode;
+        return result;
     }
 }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 81eb09c..00b49e8 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -91,6 +91,7 @@
     private static boolean mPreloaded;
 
     /*package*/ final TypedValue mTmpValue = new TypedValue();
+    /*package*/ final Configuration mTmpConfig = new Configuration();
 
     // These are protected by the mTmpValue lock.
     private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
@@ -1400,18 +1401,30 @@
      */
     public void updateConfiguration(Configuration config,
             DisplayMetrics metrics) {
+        updateConfiguration(config, metrics, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void updateConfiguration(Configuration config,
+            DisplayMetrics metrics, CompatibilityInfo compat) {
         synchronized (mTmpValue) {
+            if (compat != null) {
+                mCompatibilityInfo = compat;
+            }
             int configChanges = 0xfffffff;
             if (config != null) {
-                configChanges = mConfiguration.updateFrom(config);
+                mTmpConfig.setTo(config);
+                mCompatibilityInfo.applyToConfiguration(mTmpConfig);
+                configChanges = mConfiguration.updateFrom(mTmpConfig);
             }
             if (mConfiguration.locale == null) {
                 mConfiguration.locale = Locale.getDefault();
             }
             if (metrics != null) {
                 mMetrics.setTo(metrics);
-                mMetrics.updateMetrics(mCompatibilityInfo,
-                        mConfiguration.orientation, mConfiguration.screenLayout);
+                mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
             }
             mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
 
@@ -1500,15 +1513,23 @@
      *
      * @hide
      */
-    public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+    public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics,
+            CompatibilityInfo compat) {
         if (mSystem != null) {
-            mSystem.updateConfiguration(config, metrics);
+            mSystem.updateConfiguration(config, metrics, compat);
             //Log.i(TAG, "Updated system resources " + mSystem
             //        + ": " + mSystem.getConfiguration());
         }
     }
 
     /**
+     * @hide
+     */
+    public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+        updateSystemConfiguration(config, metrics, null);
+    }
+    
+    /**
      * Return the current display metrics that are in effect for this resource 
      * object.  The returned object should be treated as read-only.
      *