diff options
33 files changed, 635 insertions, 348 deletions
diff --git a/api/current.txt b/api/current.txt index 9468c80..231f131 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27388,6 +27388,7 @@ package android.view { method public boolean canScrollHorizontally(int); method public boolean canScrollVertically(int); method public void cancelLongPress(); + method public final void cancelPendingInputEvents(); method public boolean checkInputConnectionProxy(android.view.View); method public void clearAnimation(); method public void clearFocus(); @@ -27615,6 +27616,7 @@ package android.view { method protected void onAnimationEnd(); method protected void onAnimationStart(); method protected void onAttachedToWindow(); + method public void onCancelPendingInputEvents(); method public boolean onCheckIsTextEditor(); method protected void onConfigurationChanged(android.content.res.Configuration); method protected void onCreateContextMenu(android.view.ContextMenu); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e02410a..57686a4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -17,6 +17,7 @@ package android.app; import android.util.ArrayMap; +import android.util.SuperNotCalledException; import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; @@ -3436,6 +3437,12 @@ public class Activity extends ContextThemeWrapper // activity is finished, no matter what happens to it. mStartedActivity = true; } + + final View decor = mWindow != null ? mWindow.peekDecorView() : null; + if (decor != null) { + decor.cancelPendingInputEvents(); + } + // TODO Consider clearing/flushing other event sources and events for child windows. } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e6960b3..018fbe0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -76,6 +76,7 @@ import android.util.Log; import android.util.LogPrinter; import android.util.PrintWriterPrinter; import android.util.Slog; +import android.util.SuperNotCalledException; import android.view.Display; import android.view.HardwareRenderer; import android.view.View; @@ -116,12 +117,6 @@ import libcore.io.IoUtils; import dalvik.system.CloseGuard; -final class SuperNotCalledException extends AndroidRuntimeException { - public SuperNotCalledException(String msg) { - super(msg); - } -} - final class RemoteServiceException extends AndroidRuntimeException { public RemoteServiceException(String msg) { super(msg); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 5c49dd2..e776a98 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -710,7 +710,7 @@ class ContextImpl extends Context { @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; - synchronized (mSync) { + synchronized (ContextImpl.class) { if (sSharedPrefs == null) { sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); } diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index f8a1d82..d626e5f 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -31,6 +31,7 @@ import android.util.AttributeSet; import android.util.DebugUtils; import android.util.Log; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index a7789d6..4371907 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -31,6 +31,7 @@ import android.util.DebugUtils; import android.util.Log; import android.util.LogWriter; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index f4cc767..b1e427e 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -556,7 +556,7 @@ public final class PrintAttributes implements Parcelable { * @return New instance in landscape orientation. */ public MediaSize asPortrait() { - return new MediaSize(mId, mPackageName, mLabel, + return new MediaSize(mId, mLabel, mPackageName, Math.min(mWidthMils, mHeightMils), Math.max(mWidthMils, mHeightMils), mLabelResId); @@ -569,7 +569,7 @@ public final class PrintAttributes implements Parcelable { * @return New instance in landscape orientation. */ public MediaSize asLandscape() { - return new MediaSize(mId, mLabel, + return new MediaSize(mId, mLabel, mPackageName, Math.max(mWidthMils, mHeightMils), Math.min(mWidthMils, mHeightMils), mLabelResId); diff --git a/core/java/android/util/SuperNotCalledException.java b/core/java/android/util/SuperNotCalledException.java new file mode 100644 index 0000000..1836142 --- /dev/null +++ b/core/java/android/util/SuperNotCalledException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 The Android Open Source 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 android.util; + +/** + * @hide + */ +public final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { + super(msg); + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ce9cdfa..8616aba 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -57,6 +57,7 @@ import android.util.LongSparseLongArray; import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; +import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; import android.view.AccessibilityIterators.TextSegmentIterator; @@ -2204,6 +2205,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8; + /** + * Flag indicating that an overridden method correctly called down to + * the superclass implementation as required by the API spec. + */ + static final int PFLAG3_CALLED_SUPER = 0x10; + /* End of masks for mPrivateFlags3 */ @@ -5955,6 +5962,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Invalidate too, since the default behavior for views is to be // be drawn at 50% alpha rather than to change the drawable. invalidate(true); + + if (!enabled) { + cancelPendingInputEvents(); + } } /** @@ -12364,6 +12375,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Cancel any deferred high-level input events that were previously posted to the event queue. + * + * <p>Many views post high-level events such as click handlers to the event queue + * to run deferred in order to preserve a desired user experience - clearing visible + * pressed states before executing, etc. This method will abort any events of this nature + * that are currently in flight.</p> + * + * <p>Custom views that generate their own high-level deferred input events should override + * {@link #onCancelPendingInputEvents()} and remove those pending events from the queue.</p> + * + * <p>This will also cancel pending input events for any child views.</p> + * + * <p>Note that this may not be sufficient as a debouncing strategy for clicks in all cases. + * This will not impact newer events posted after this call that may occur as a result of + * lower-level input events still waiting in the queue. If you are trying to prevent + * double-submitted events for the duration of some sort of asynchronous transaction + * you should also take other steps to protect against unexpected double inputs e.g. calling + * {@link #setEnabled(boolean) setEnabled(false)} and re-enabling the view when + * the transaction completes, tracking already submitted transaction IDs, etc.</p> + */ + public final void cancelPendingInputEvents() { + dispatchCancelPendingInputEvents(); + } + + /** + * Called by {@link #cancelPendingInputEvents()} to cancel input events in flight. + * Overridden by ViewGroup to dispatch. Package scoped to prevent app-side meddling. + */ + void dispatchCancelPendingInputEvents() { + mPrivateFlags3 &= ~PFLAG3_CALLED_SUPER; + onCancelPendingInputEvents(); + if ((mPrivateFlags3 & PFLAG3_CALLED_SUPER) != PFLAG3_CALLED_SUPER) { + throw new SuperNotCalledException("View " + getClass().getSimpleName() + + " did not call through to super.onCancelPendingInputEvents()"); + } + } + + /** + * Called as the result of a call to {@link #cancelPendingInputEvents()} on this view or + * a parent view. + * + * <p>This method is responsible for removing any pending high-level input events that were + * posted to the event queue to run later. Custom view classes that post their own deferred + * high-level events via {@link #post(Runnable)}, {@link #postDelayed(Runnable, long)} or + * {@link android.os.Handler} should override this method, call + * <code>super.onCancelPendingInputEvents()</code> and remove those callbacks as appropriate. + * </p> + */ + public void onCancelPendingInputEvents() { + removePerformClickCallback(); + cancelLongPress(); + mPrivateFlags3 |= PFLAG3_CALLED_SUPER; + } + + /** * Store this view hierarchy's frozen state into the given container. * * @param container The SparseArray in which to save the view's state. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 2d75b06..faeee3f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3182,6 +3182,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + void dispatchCancelPendingInputEvents() { + super.dispatchCancelPendingInputEvents(); + + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].dispatchCancelPendingInputEvents(); + } + } + /** * When this property is set to true, this ViewGroup supports static transformations on * children; this causes diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c308024..29b7cf2 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2843,6 +2843,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return new AdapterContextMenuInfo(view, position, id); } + @Override + public void onCancelPendingInputEvents() { + super.onCancelPendingInputEvents(); + if (mPerformClick != null) { + removeCallbacks(mPerformClick); + } + if (mPendingCheckForTap != null) { + removeCallbacks(mPendingCheckForTap); + } + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + if (mPendingCheckForKeyLongPress != null) { + removeCallbacks(mPendingCheckForKeyLongPress); + } + } + /** * A base class for Runnables that will check that their view is still attached to * the original window as when the Runnable was created. diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java index 88da21f..7d6ca56 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -257,8 +257,96 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private boolean mReadHistoryCompleted; private boolean mReadHistoryInProgress; - private final AsyncTask<Void, Void, List<PrinterInfo>> mReadTask = - new AsyncTask<Void, Void, List<PrinterInfo>>() { + private ReadTask mReadTask; + + private PersistenceManager(Context context) { + mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), + PERSIST_FILE_NAME)); + } + + public boolean isReadHistoryInProgress() { + return mReadHistoryInProgress; + } + + public boolean isReadHistoryCompleted() { + return mReadHistoryCompleted; + } + + public boolean stopReadPrinterHistory() { + final boolean cancelled = mReadTask.cancel(true); + mReadTask = null; + return cancelled; + } + + public void readPrinterHistory() { + if (DEBUG) { + Log.i(LOG_TAG, "read history started"); + } + mReadHistoryInProgress = true; + mReadTask = new ReadTask(); + mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + } + + @SuppressWarnings("unchecked") + public void addPrinterAndWritePrinterHistory(PrinterInfo printer) { + if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { + mHistoricalPrinters.remove(0); + } + mHistoricalPrinters.add(printer); + new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<PrinterInfo>(mHistoricalPrinters)); + } + + private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { + Map<PrinterId, PrinterRecord> recordMap = + new ArrayMap<PrinterId, PrinterRecord>(); + + // Recompute the weights. + float currentWeight = 1.0f; + final int printerCount = printers.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo printer = printers.get(i); + // Aggregate weight for the same printer + PrinterRecord record = recordMap.get(printer.getId()); + if (record == null) { + record = new PrinterRecord(printer); + recordMap.put(printer.getId(), record); + } + record.weight += currentWeight; + currentWeight *= WEIGHT_DECAY_COEFFICIENT; + } + + // Soft the favorite printers. + List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>( + recordMap.values()); + Collections.sort(favoriteRecords); + + // Write the favorites to the output. + final int favoriteCount = favoriteRecords.size(); + List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount); + for (int i = 0; i < favoriteCount; i++) { + PrinterInfo printer = favoriteRecords.get(i).printer; + favoritePrinters.add(printer); + } + + return favoritePrinters; + } + + private final class PrinterRecord implements Comparable<PrinterRecord> { + public final PrinterInfo printer; + public float weight; + + public PrinterRecord(PrinterInfo printer) { + this.printer = printer; + } + + @Override + public int compareTo(PrinterRecord another) { + return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); + } + } + + private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> { @Override protected List<PrinterInfo> doInBackground(Void... args) { return doReadPrinterHistory(); @@ -284,6 +372,9 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { // Start loading the available printers. loadInternal(); + + // We are done. + mReadTask = null; } private List<PrinterInfo> doReadPrinterHistory() { @@ -411,8 +502,7 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { } }; - private final AsyncTask<List<PrinterInfo>, Void, Void> mWriteTask = - new AsyncTask<List<PrinterInfo>, Void, Void>() { + private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> { @Override protected Void doInBackground(List<PrinterInfo>... printers) { doWritePrinterHistory(printers[0]); @@ -473,89 +563,5 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { } } }; - - private PersistenceManager(Context context) { - mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), - PERSIST_FILE_NAME)); - } - - public boolean isReadHistoryInProgress() { - return mReadHistoryInProgress; - } - - public boolean isReadHistoryCompleted() { - return mReadHistoryCompleted; - } - - public boolean stopReadPrinterHistory() { - return mReadTask.cancel(true); - } - - public void readPrinterHistory() { - if (DEBUG) { - Log.i(LOG_TAG, "read history started"); - } - mReadHistoryInProgress = true; - mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); - } - - @SuppressWarnings("unchecked") - public void addPrinterAndWritePrinterHistory(PrinterInfo printer) { - if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { - mHistoricalPrinters.remove(0); - } - mHistoricalPrinters.add(printer); - mWriteTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, - new ArrayList<PrinterInfo>(mHistoricalPrinters)); - } - - private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { - Map<PrinterId, PrinterRecord> recordMap = - new ArrayMap<PrinterId, PrinterRecord>(); - - // Recompute the weights. - float currentWeight = 1.0f; - final int printerCount = printers.size(); - for (int i = printerCount - 1; i >= 0; i--) { - PrinterInfo printer = printers.get(i); - // Aggregate weight for the same printer - PrinterRecord record = recordMap.get(printer.getId()); - if (record == null) { - record = new PrinterRecord(printer); - recordMap.put(printer.getId(), record); - } - record.weight += currentWeight; - currentWeight *= WEIGHT_DECAY_COEFFICIENT; - } - - // Soft the favorite printers. - List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>( - recordMap.values()); - Collections.sort(favoriteRecords); - - // Write the favorites to the output. - final int favoriteCount = favoriteRecords.size(); - List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount); - for (int i = 0; i < favoriteCount; i++) { - PrinterInfo printer = favoriteRecords.get(i).printer; - favoritePrinters.add(printer); - } - - return favoritePrinters; - } - - private final class PrinterRecord implements Comparable<PrinterRecord> { - public final PrinterInfo printer; - public float weight; - - public PrinterRecord(PrinterInfo printer) { - this.printer = printer; - } - - @Override - public int compareTo(PrinterRecord another) { - return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight); - } - } } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 929a04e..1040edf 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -100,8 +100,6 @@ public class PrintJobConfigActivity extends Activity { private static final boolean DEBUG = true && Build.IS_DEBUGGABLE; - private static final boolean LIVE_PREVIEW_SUPPORTED = false; - public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter"; public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes"; public static final String EXTRA_PRINT_JOB_ID = "printJobId"; @@ -133,8 +131,7 @@ public class PrintJobConfigActivity extends Activity { private static final int EDITOR_STATE_INITIALIZED = 1; private static final int EDITOR_STATE_CONFIRMED_PRINT = 2; -// private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3; - private static final int EDITOR_STATE_CANCELLED = 4; + private static final int EDITOR_STATE_CANCELLED = 3; private static final int MIN_COPIES = 1; private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES); @@ -240,8 +237,7 @@ public class PrintJobConfigActivity extends Activity { } public boolean onTouchEvent(MotionEvent event) { - if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed() - && mEditor.shouldCloseOnTouch(event)) { + if (!mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) { if (!mController.isWorking()) { PrintJobConfigActivity.this.finish(); } @@ -424,19 +420,7 @@ public class PrintJobConfigActivity extends Activity { if (!infoChanged && !layoutChanged && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) { if (mEditor.isDone()) { - PrintJobConfigActivity.this.finish(); - } - return; - } - - // If we do not support live preview and the current layout is - // not for preview purposes, i.e. the user did not poke the - // preview button, then just skip the write. - if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed() - && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) { - mEditor.updateUi(); - if (mEditor.isDone()) { - PrintJobConfigActivity.this.finish(); + requestCreatePdfFileOrFinish(); } return; } @@ -526,16 +510,20 @@ public class PrintJobConfigActivity extends Activity { } if (mEditor.isDone()) { - if (mEditor.isPrintingToPdf()) { - PrintJobInfo printJob = PrintSpoolerService.peekInstance() - .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.setType("application/pdf"); - intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel()); - startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); - } else { - PrintJobConfigActivity.this.finish(); - } + requestCreatePdfFileOrFinish(); + } + } + + private void requestCreatePdfFileOrFinish() { + if (mEditor.isPrintingToPdf()) { + PrintJobInfo printJob = PrintSpoolerService.peekInstance() + .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.setType("application/pdf"); + intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel()); + startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); + } else { + PrintJobConfigActivity.this.finish(); } } @@ -1101,7 +1089,8 @@ public class PrintJobConfigActivity extends Activity { public void addCurrentPrinterToHistory() { PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem(); - if (printer != null) { + PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId(); + if (printer != null && !printer.getId().equals(fakePdfPritnerId)) { FusedPrintersProvider printersLoader = (FusedPrintersProvider) (Loader<?>) getLoaderManager().getLoader( LOADER_ID_PRINTERS_LOADER); @@ -1324,7 +1313,7 @@ public class PrintJobConfigActivity extends Activity { } public boolean isDone() { - return isPrintConfirmed() || isPreviewConfirmed() || isCancelled(); + return isPrintConfirmed() || isCancelled(); } public boolean isPrintConfirmed() { @@ -1337,10 +1326,6 @@ public class PrintJobConfigActivity extends Activity { showUi(UI_GENERATING_PRINT_JOB, null); } - public boolean isPreviewConfirmed() { - return mEditorState == EDITOR_STATE_CONFIRMED_PRINT; - } - public PageRange[] getRequestedPages() { if (hasErrors()) { return null; @@ -1450,7 +1435,7 @@ public class PrintJobConfigActivity extends Activity { if (mCurrentUi != UI_EDITING_PRINT_JOB) { return false; } - if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) { + if (isPrintConfirmed() || isCancelled()) { mDestinationSpinner.setEnabled(false); mCopiesEditText.setEnabled(false); mMediaSizeSpinner.setEnabled(false); @@ -1498,9 +1483,9 @@ public class PrintJobConfigActivity extends Activity { mColorModeSpinner.setEnabled(false); // Orientation - if (mOrientationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) { + if (mOrientationSpinner.getSelectedItemPosition() != 0) { mIgnoreNextOrientationChange = true; - mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION); + mOrientationSpinner.setSelection(0); } mOrientationSpinner.setEnabled(false); @@ -1768,7 +1753,7 @@ public class PrintJobConfigActivity extends Activity { mIgnoreNextColorModeChange = true; mColorModeSpinnerAdapter.clear(); } - if (!mOrientationSpinnerAdapter.isEmpty()) { + if (mOrientationSpinner.getSelectedItemPosition() != 0) { mIgnoreNextOrientationChange = true; mOrientationSpinner.setSelection(0); } diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath index 3c124d9..2e4274d 100644 --- a/tools/layoutlib/bridge/.classpath +++ b/tools/layoutlib/bridge/.classpath @@ -7,5 +7,6 @@ <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index 687a91f..e3d48fc 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -22,6 +22,7 @@ LOCAL_JAVA_RESOURCE_DIRS := resources LOCAL_JAVA_LIBRARIES := \ kxml2-2.3.0 \ + icu4j \ layoutlib_api-prebuilt \ tools-common-prebuilt diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..782ebfe --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..677b471 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..a1b8062 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..fcdbefe --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..633d864 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..4665e2a --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java new file mode 100644 index 0000000..62d0a0d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2013 The Android Open Source 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 android.graphics; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.util.LinkedList; +import java.util.List; + +import com.ibm.icu.lang.UScript; +import com.ibm.icu.lang.UScriptRun; + +import android.graphics.Paint_Delegate.FontInfo; + +/** + * Render the text by breaking it into various scripts and using the right font for each script. + * Can be used to measure the text without actually drawing it. + */ +@SuppressWarnings("deprecation") +public class BidiRenderer { + + /* package */ static class ScriptRun { + int start; + int limit; + boolean isRtl; + int scriptCode; + FontInfo font; + + public ScriptRun(int start, int limit, boolean isRtl) { + this.start = start; + this.limit = limit; + this.isRtl = isRtl; + this.scriptCode = UScript.INVALID_CODE; + } + } + + /* package */ Graphics2D graphics; + /* package */ Paint_Delegate paint; + /* package */ char[] text; + + /** + * @param graphics May be null. + * @param paint The Paint to use to get the fonts. Should not be null. + * @param text Unidirectional text. Should not be null. + */ + /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { + assert (paint != null); + this.graphics = graphics; + this.paint = paint; + this.text = text; + } + + /** + * Render unidirectional text. + * + * This method can also be used to measure the width of the text without actually drawing it. + * + * @param start index of the first character + * @param limit index of the first character that should not be rendered. + * @param isRtl is the text right-to-left + * @param advances If not null, then advances for each character to be rendered are returned + * here. + * @param advancesIndex index into advances from where the advances need to be filled. + * @param draw If true and {@link graphics} is not null, draw the rendered text on the graphics + * at the given co-ordinates + * @param x The x-coordinate of the left edge of where the text should be drawn on the given + * graphics. + * @param y The y-coordinate at which to draw the text on the given graphics. + * @return The x-coordinate of the right edge of the drawn text. In other words, + * x + the width of the text. + */ + /* package */ float renderText(int start, int limit, boolean isRtl, float advances[], + int advancesIndex, boolean draw, float x, float y) { + // We break the text into scripts and then select font based on it and then render each of + // the script runs. + for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) { + int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT; + flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT; + x = renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw, + x, y); + advancesIndex += run.limit - run.start; + } + return x; + } + + /** + * Render a script run. Use the preferred font to render as much as possible. This also + * implements a fallback mechanism to render characters that cannot be drawn using the + * preferred font. + * + * @return x + width of the text drawn. + */ + private float renderScript(int start, int limit, FontInfo preferredFont, int flag, + float advances[], int advancesIndex, boolean draw, float x, float y) { + List<FontInfo> fonts = paint.getFonts(); + if (fonts == null || preferredFont == null) { + return x; + } + + while (start < limit) { + boolean foundFont = false; + int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit); + if (canDisplayUpTo == -1) { + return render(start, limit, preferredFont, flag, advances, advancesIndex, draw, + x, y); + } else if (canDisplayUpTo > start) { // can draw something + x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, + draw, x, y); + advancesIndex += canDisplayUpTo - start; + start = canDisplayUpTo; + } + + int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1; + for (FontInfo font : fonts) { + canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount); + if (canDisplayUpTo == -1) { + x = render(start, start+charCount, font, flag, advances, advancesIndex, draw, + x, y); + start += charCount; + advancesIndex += charCount; + foundFont = true; + break; + } + } + if (!foundFont) { + // No font can display this char. Use the preferred font. The char will most + // probably appear as a box or a blank space. We could, probably, use some + // heuristics and break the character into the base character and diacritics and + // then draw it, but it's probably not worth the effort. + x = render(start, start + charCount, preferredFont, flag, advances, advancesIndex, + draw, x, y); + start += charCount; + advancesIndex += charCount; + } + } + return x; + } + + /** + * Render the text with the given font. + */ + private float render(int start, int limit, FontInfo font, int flag, float advances[], + int advancesIndex, boolean draw, float x, float y) { + + float totalAdvance = 0; + // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with + // the anti-aliasing set. + FontRenderContext f = font.mMetrics.getFontRenderContext(); + FontRenderContext frc = new FontRenderContext(f.getTransform(), paint.isAntiAliased(), + f.usesFractionalMetrics()); + GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag); + int ng = gv.getNumGlyphs(); + int[] ci = gv.getGlyphCharIndices(0, ng, null); + for (int i = 0; i < ng; i++) { + float adv = gv.getGlyphMetrics(i).getAdvanceX(); + if (advances != null) { + int adv_idx = advancesIndex + ci[i]; + advances[adv_idx] += adv; + } + totalAdvance += adv; + } + if (draw && graphics != null) { + graphics.drawGlyphVector(gv, x, y); + } + return x + totalAdvance; + } + + // --- Static helper methods --- + + /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, + boolean isRtl, List<FontInfo> fonts) { + LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>(); + + int count = limit - start; + UScriptRun uScriptRun = new UScriptRun(text, start, count); + while (uScriptRun.next()) { + int scriptStart = uScriptRun.getScriptStart(); + int scriptLimit = uScriptRun.getScriptLimit(); + ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl); + run.scriptCode = uScriptRun.getScriptCode(); + setScriptFont(text, run, fonts); + scriptRuns.add(run); + } + + return scriptRuns; + } + + // TODO: Replace this method with one which returns the font based on the scriptCode. + private static void setScriptFont(char[] text, ScriptRun run, + List<FontInfo> fonts) { + for (FontInfo fontInfo : fonts) { + if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) { + run.font = fontInfo; + return; + } + } + run.font = fonts.get(0); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 361f5d7..62b47bd 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -23,7 +23,6 @@ import com.android.layoutlib.bridge.impl.GcSnapshot; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.graphics.Bitmap.Config; -import android.graphics.Paint_Delegate.FontInfo; import android.text.TextUtils; import java.awt.Color; @@ -35,7 +34,6 @@ import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.image.BufferedImage; -import java.util.List; /** @@ -978,7 +976,8 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void native_drawText(int nativeCanvas, final char[] text, final int index, final int count, - final float startX, final float startY, int flags, int paint) { + final float startX, final float startY, final int flags, int paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -988,10 +987,10 @@ public final class Canvas_Delegate { // Paint.TextAlign indicates how the text is positioned relative to X. // LEFT is the default and there's nothing to do. float x = startX; - float y = startY; + int limit = index + count; + boolean isRtl = flags == Canvas.DIRECTION_RTL; if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - // TODO: check the value of bidiFlags. - float m = paintDelegate.measureText(text, index, count, 0); + float m = paintDelegate.measureText(text, index, count, isRtl); if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { x -= m / 2; } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { @@ -999,87 +998,15 @@ public final class Canvas_Delegate { } } - List<FontInfo> fonts = paintDelegate.getFonts(); - - if (fonts.size() > 0) { - FontInfo mainFont = fonts.get(0); - int i = index; - int lastIndex = index + count; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // draw all the rest and exit. - graphics.setFont(mainFont.mFont); - graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y); - return; - } else if (upTo > 0) { - // draw what's possible - graphics.setFont(mainFont.mFont); - graphics.drawChars(text, i, upTo - i, (int)x, (int)y); - - // compute the width that was drawn to increase x - x += mainFont.mMetrics.charsWidth(text, i, upTo - i); - - // move index to the first non displayed char. - i = upTo; - - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < fonts.size() ; f++) { - FontInfo fontInfo = fonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - // draw that char - graphics.setFont(fontInfo.mFont); - graphics.drawChars(text, i, charCount, (int)x, (int)y); - - // update x - x += fontInfo.mMetrics.charsWidth(text, i, charCount); - - // update the index in the text, and move on - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, display it with the main font. - // (it'll put a square probably) - if (foundFont == false) { - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - - graphics.setFont(mainFont.mFont); - graphics.drawChars(text, i, charCount, (int)x, (int)y); - - // measure it to advance x - x += mainFont.mMetrics.charsWidth(text, i, charCount); - - // and move to the next chars. - i += charCount; - } - } - } + new BidiRenderer(graphics, paintDelegate, text).renderText( + index, limit, isRtl, null, 0, true, x, startY); } }); } @LayoutlibDelegate /*package*/ static void native_drawText(int nativeCanvas, String text, - int start, int end, float x, float y, int flags, int paint) { + int start, int end, float x, float y, final int flags, int paint) { int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index c9c9800..41953ed 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -32,7 +32,6 @@ import java.awt.Stroke; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -576,7 +575,7 @@ public class Paint_Delegate { return 0; } - return delegate.measureText(text, index, count, bidiFlags); + return delegate.measureText(text, index, count, isRtl(bidiFlags)); } @LayoutlibDelegate @@ -615,7 +614,7 @@ public class Paint_Delegate { } // measure from start to end - float res = delegate.measureText(text, start, end - start + 1, bidiFlags); + float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); if (measuredWidth != null) { measuredWidth[measureIndex] = res; @@ -980,51 +979,27 @@ public class Paint_Delegate { /*package*/ static float native_getTextRunAdvances(int native_object, char[] text, int index, int count, int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex) { + + if (advances != null) + for (int i = advancesIndex; i< advancesIndex+count; i++) + advances[i]=0; // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null) { + if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { return 0.f; } + boolean isRtl = isRtl(flags); - if (delegate.mFonts.size() > 0) { - // FIXME: handle multi-char characters (see measureText) - float totalAdvance = 0; - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : delegate.mFonts) { - if (info.mFont.canDisplay(c)) { - float adv = info.mMetrics.charWidth(c); - totalAdvance += adv; - if (advances != null) { - advances[i] = adv; - } - - found = true; - break; - } - } - - if (found == false) { - // no advance for this char. - if (advances != null) { - advances[i] = 0.f; - } - } - } - - return totalAdvance; - } - - return 0; - + int limit = index + count; + return new BidiRenderer(null, delegate, text).renderText( + index, limit, isRtl, advances, advancesIndex, false, 0, 0); } @LayoutlibDelegate /*package*/ static float native_getTextRunAdvances(int native_object, String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex) { - // FIXME: support contextStart, contextEnd and direction flag + // FIXME: support contextStart and contextEnd int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); @@ -1080,19 +1055,12 @@ public class Paint_Delegate { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); - if (delegate == null) { + if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { return; } - - // FIXME should test if the main font can display all those characters. - // See MeasureText - if (delegate.mFonts.size() > 0) { - FontInfo mainInfo = delegate.mFonts.get(0); - - Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, - delegate.mFontContext); - bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight()); - } + int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags)); + int h= delegate.getFonts().get(0).mMetrics.getHeight(); + bounds.set(0, 0, w, h); } @LayoutlibDelegate @@ -1176,6 +1144,7 @@ public class Paint_Delegate { info.mFont = info.mFont.deriveFont(new AffineTransform( mTextScaleX, mTextSkewX, 0, 1, 0, 0)); } + // The metrics here don't have anti-aliasing set. info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); infoList.add(info); @@ -1185,64 +1154,9 @@ public class Paint_Delegate { } } - /*package*/ float measureText(char[] text, int index, int count, int bidiFlags) { - // TODO: find out what bidiFlags actually does. - - // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText - // Any change to this method should be reflected there as well - - if (mFonts.size() > 0) { - FontInfo mainFont = mFonts.get(0); - int i = index; - int lastIndex = index + count; - float total = 0f; - while (i < lastIndex) { - // always start with the main font. - int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); - if (upTo == -1) { - // shortcut to exit - return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); - } else if (upTo > 0) { - total += mainFont.mMetrics.charsWidth(text, i, upTo - i); - i = upTo; - // don't call continue at this point. Since it is certain the main font - // cannot display the font a index upTo (now ==i), we move on to the - // fallback fonts directly. - } - - // no char supported, attempt to read the next char(s) with the - // fallback font. In this case we only test the first character - // and then go back to test with the main font. - // Special test for 2-char characters. - boolean foundFont = false; - for (int f = 1 ; f < mFonts.size() ; f++) { - FontInfo fontInfo = mFonts.get(f); - - // need to check that the font can display the character. We test - // differently if the char is a high surrogate. - int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; - upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); - if (upTo == -1) { - total += fontInfo.mMetrics.charsWidth(text, i, charCount); - i += charCount; - foundFont = true; - break; - - } - } - - // in case no font can display the char, measure it with the main font. - if (foundFont == false) { - int size = Character.isHighSurrogate(text[i]) ? 2 : 1; - total += mainFont.mMetrics.charsWidth(text, i, size); - i += size; - } - } - - return total; - } - - return 0; + /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) { + return new BidiRenderer(null, this, text).renderText( + index, index + count, isRtl, null, 0, false, 0, 0); } private float getFontMetrics(FontMetrics metrics) { @@ -1281,4 +1195,14 @@ public class Paint_Delegate { } } + private static boolean isRtl(int flag) { + switch(flag) { + case Paint.BIDI_RTL: + case Paint.BIDI_FORCE_RTL: + case Paint.BIDI_DEFAULT_RTL: + return true; + default: + return false; + } + } } diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java index 52b8f34..973fa0e 100644 --- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java @@ -16,7 +16,10 @@ package android.text; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import com.ibm.icu.text.Bidi; /** @@ -29,9 +32,29 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; public class AndroidBidi_Delegate { @LayoutlibDelegate - /*package*/ static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) { - // return the equivalent of Layout.DIR_LEFT_TO_RIGHT - // TODO: actually figure the direction. - return 0; + /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count, + boolean haveInfo) { + + switch (dir) { + case 0: // Layout.DIR_REQUEST_LTR + case 1: // Layout.DIR_REQUEST_RTL + break; // No change. + case -1: + dir = Bidi.LEVEL_DEFAULT_LTR; + break; + case -2: + dir = Bidi.LEVEL_DEFAULT_RTL; + break; + default: + // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue. + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null); + dir = Bidi.LEVEL_DEFAULT_LTR; + } + Bidi bidi = new Bidi(chars, 0, null, 0, count, dir); + if (charInfo != null) { + for (int i = 0; i < count; ++i) + charInfo[i] = bidi.getLevelAt(i); + } + return bidi.getParaLevel(); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 42257c5..ab4be71 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -35,6 +35,7 @@ import com.android.resources.ResourceType; import com.android.tools.layoutlib.create.MethodAdapter; import com.android.tools.layoutlib.create.OverrideMethod; import com.android.util.Pair; +import com.ibm.icu.util.ULocale; import android.content.res.BridgeAssetManager; import android.graphics.Bitmap; @@ -64,6 +65,8 @@ import java.util.concurrent.locks.ReentrantLock; */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { + private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; + public static class StaticMethodNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -211,7 +214,8 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.ANIMATED_VIEW_MANIPULATION, Capability.ADAPTER_BINDING, Capability.EXTENDED_VIEWINFO, - Capability.FIXED_SCALABLE_NINE_PATCH); + Capability.FIXED_SCALABLE_NINE_PATCH, + Capability.RTL); BridgeAssetManager.initSystem(); @@ -411,6 +415,20 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { throw new IllegalArgumentException("viewObject is not a View"); } + @Override + public boolean isRtl(String locale) { + return isLocaleRtl(locale); + } + + public static boolean isLocaleRtl(String locale) { + if (locale == null) { + locale = ""; + } + ULocale uLocale = new ULocale(locale); + return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ? + true : false; + } + /** * Returns the lock for the bridge */ diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index d63dcac..a1f2697 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -132,7 +132,8 @@ public final class BridgeContext extends Context { RenderResources renderResources, IProjectCallback projectCallback, Configuration config, - int targetSdkVersion) { + int targetSdkVersion, + boolean hasRtlSupport) { mProjectKey = projectKey; mMetrics = metrics; mProjectCallback = projectCallback; @@ -142,6 +143,9 @@ public final class BridgeContext extends Context { mApplicationInfo = new ApplicationInfo(); mApplicationInfo.targetSdkVersion = targetSdkVersion; + if (hasRtlSupport) { + mApplicationInfo.flags = mApplicationInfo.flags | ApplicationInfo.FLAG_SUPPORTS_RTL; + } mWindowManager = new WindowManagerImpl(mMetrics); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index ea9d8d9..17b0eb6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -25,6 +25,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.Density; +import com.android.resources.LayoutDirection; import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; @@ -86,38 +87,53 @@ abstract class CustomBar extends LinearLayout { } } - private InputStream getIcon(String iconName, Density[] densityInOut, String[] pathOut, - boolean tryOtherDensities) { + private InputStream getIcon(String iconName, Density[] densityInOut, LayoutDirection direction, + String[] pathOut, boolean tryOtherDensities) { // current density Density density = densityInOut[0]; // bitmap url relative to this class - pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName; + if (direction != null) { + pathOut[0] = "/bars/" + direction.getResourceValue() + "-" + density.getResourceValue() + + "/" + iconName; + } else { + pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName; + } InputStream stream = getClass().getResourceAsStream(pathOut[0]); if (stream == null && tryOtherDensities) { for (Density d : Density.values()) { if (d != density) { densityInOut[0] = d; - stream = getIcon(iconName, densityInOut, pathOut, false /*tryOtherDensities*/); + stream = getIcon(iconName, densityInOut, direction, pathOut, + false /*tryOtherDensities*/); if (stream != null) { return stream; } } } + // couldn't find resource with direction qualifier. try without. + if (direction != null) { + return getIcon(iconName, densityInOut, null, pathOut, true); + } } return stream; } protected void loadIcon(int index, String iconName, Density density) { + loadIcon(index, iconName, density, false); + } + + protected void loadIcon(int index, String iconName, Density density, boolean isRtl) { View child = getChildAt(index); if (child instanceof ImageView) { ImageView imageView = (ImageView) child; String[] pathOut = new String[1]; Density[] densityInOut = new Density[] { density }; - InputStream stream = getIcon(iconName, densityInOut, pathOut, + LayoutDirection dir = isRtl ? LayoutDirection.RTL : LayoutDirection.LTR; + InputStream stream = getIcon(iconName, densityInOut, dir, pathOut, true /*tryOtherDensities*/); density = densityInOut[0]; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java index cc90d6b..84e676e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java @@ -17,6 +17,7 @@ package com.android.layoutlib.bridge.bars; import com.android.resources.Density; +import com.android.layoutlib.bridge.Bridge; import org.xmlpull.v1.XmlPullParserException; @@ -26,7 +27,8 @@ import android.widget.TextView; public class NavigationBar extends CustomBar { - public NavigationBar(Context context, Density density, int orientation) throws XmlPullParserException { + public NavigationBar(Context context, Density density, int orientation, boolean isRtl, + boolean rtlEnabled) throws XmlPullParserException { super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml"); setBackgroundColor(0xFF000000); @@ -37,14 +39,15 @@ public class NavigationBar extends CustomBar { // 0 is a spacer. int back = 1; int recent = 3; - if (orientation == LinearLayout.VERTICAL) { + if (orientation == LinearLayout.VERTICAL || (isRtl && !rtlEnabled)) { + // If RTL is enabled, then layoutlib mirrors the layout for us. back = 3; recent = 1; } - loadIcon(back, "ic_sysbar_back.png", density); - loadIcon(2, "ic_sysbar_home.png", density); - loadIcon(recent, "ic_sysbar_recent.png", density); + loadIcon(back, "ic_sysbar_back.png", density, isRtl); + loadIcon(2, "ic_sysbar_home.png", density, isRtl); + loadIcon(recent, "ic_sysbar_recent.png", density, isRtl); } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index 5c08412..baa956d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -30,7 +30,10 @@ import android.widget.TextView; public class StatusBar extends CustomBar { - public StatusBar(Context context, Density density) throws XmlPullParserException { + public StatusBar(Context context, Density density, int direction, boolean RtlEnabled) + throws XmlPullParserException { + // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. + super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml"); // FIXME: use FILL_H? diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java index 081ce67..108b651 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java @@ -52,6 +52,8 @@ public final class FontLoader { private static final String NODE_NAME = "name"; private static final String NODE_FILE = "file"; + private static final String ATTRIBUTE_VARIANT = "variant"; + private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant"; private static final String FONT_SUFFIX_NONE = ".ttf"; private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; @@ -189,6 +191,7 @@ public final class FontLoader { private FontInfo mFontInfo = null; private final StringBuilder mBuilder = new StringBuilder(); private List<FontInfo> mFontList = new ArrayList<FontInfo>(); + private boolean isCompactFont = true; private FontHandler(String osFontsLocation) { super(); @@ -209,8 +212,21 @@ public final class FontLoader { mFontList = new ArrayList<FontInfo>(); } else if (NODE_FAMILY.equals(localName)) { if (mFontList != null) { + mFontInfo = null; + } + } else if (NODE_NAME.equals(localName)) { + if (mFontList != null && mFontInfo == null) { + mFontInfo = new FontInfo(); + } + } else if (NODE_FILE.equals(localName)) { + if (mFontList != null && mFontInfo == null) { mFontInfo = new FontInfo(); } + if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) { + isCompactFont = false; + } else { + isCompactFont = true; + } } mBuilder.setLength(0); @@ -223,7 +239,9 @@ public final class FontLoader { */ @Override public void characters(char[] ch, int start, int length) throws SAXException { - mBuilder.append(ch, start, length); + if (isCompactFont) { + mBuilder.append(ch, start, length); + } } /* (non-Javadoc) @@ -259,7 +277,7 @@ public final class FontLoader { } } else if (NODE_FILE.equals(localName)) { // handle a new file for an existing Font Info - if (mFontInfo != null) { + if (isCompactFont && mFontInfo != null) { String fileName = trimXmlWhitespaces(mBuilder.toString()); Font font = getFont(fileName); if (font != null) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index b909bec..87047b3 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -121,7 +121,8 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso // build the context mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, - mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion()); + mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion(), + mParams.isRtlSupported()); setUp(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 6011fdb..57771e3 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -224,13 +224,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); HardwareConfig hardwareConfig = params.getHardwareConfig(); BridgeContext context = getContext(); - + boolean isRtl = Bridge.isLocaleRtl(params.getLocale()); + int direction = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; // the view group that receives the window background. ViewGroup backgroundView = null; if (mWindowIsFloating || params.isForceNoDecor()) { backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); + mViewRoot.setLayoutDirection(direction); } else { if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) { /* @@ -252,12 +254,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { the bottom */ LinearLayout topLayout = new LinearLayout(context); + topLayout.setLayoutDirection(direction); mViewRoot = topLayout; topLayout.setOrientation(LinearLayout.HORIZONTAL); try { NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.VERTICAL); + hardwareConfig.getDensity(), LinearLayout.VERTICAL, isRtl, + params.isRtlSupported()); navigationBar.setLayoutParams( new LinearLayout.LayoutParams( mNavigationBarSize, @@ -289,6 +293,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { LinearLayout topLayout = new LinearLayout(context); topLayout.setOrientation(LinearLayout.VERTICAL); + topLayout.setLayoutDirection(direction); // if we don't already have a view root this is it if (mViewRoot == null) { mViewRoot = topLayout; @@ -300,13 +305,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // this is the case of soft buttons + vertical bar. // this top layout is the first layout in the horizontal layout. see above) - mViewRoot.addView(topLayout, 0); + if (isRtl && params.isRtlSupported()) { + // If RTL is enabled, layoutlib will mirror the layouts. So, add the + // topLayout to the right of Navigation Bar and layoutlib will draw it + // to the left. + mViewRoot.addView(topLayout); + } else { + // Add the top layout to the left of the Navigation Bar. + mViewRoot.addView(topLayout, 0); + } } if (mStatusBarSize > 0) { // system bar try { - StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity()); + StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity(), + direction, params.isRtlSupported()); systemBar.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, mStatusBarSize)); @@ -365,7 +379,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // system bar try { NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.HORIZONTAL); + hardwareConfig.getDensity(), LinearLayout.HORIZONTAL, isRtl, + params.isRtlSupported()); navigationBar.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, mNavigationBarSize)); |
