diff options
231 files changed, 17072 insertions, 8786 deletions
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 26b6ad7..5a83604 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -258,6 +258,7 @@ public class SyncManager implements OnAccountsUpdateListener { // DISCONNECTED for GPRS in any order. if we receive the CONNECTED first, and then // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true // since we still have a WiFi connection. + final boolean wasConnected = mDataConnectionIsConnected; switch (state) { case CONNECTED: mDataConnectionIsConnected = true; @@ -273,6 +274,12 @@ public class SyncManager implements OnAccountsUpdateListener { // ignore the rest of the states -- leave our boolean alone. } if (mDataConnectionIsConnected) { + if (!wasConnected) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Reconnection detected: clearing all backoffs"); + } + mSyncStorageEngine.clearAllBackoffs(); + } sendCheckAlarmsMessage(); } } diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index e1a9dbb..faf1365 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -525,6 +525,32 @@ public class SyncStorageEngine extends Handler { } } + public void clearAllBackoffs() { + boolean changed = false; + synchronized (mAuthorities) { + for (AccountInfo accountInfo : mAccounts.values()) { + for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { + if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE + || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "clearAllBackoffs:" + + " authority:" + authorityInfo.authority + + " account:" + accountInfo.account.name + + " backoffTime was: " + authorityInfo.backoffTime + + " backoffDelay was: " + authorityInfo.backoffDelay); + } + authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; + authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; + changed = true; + } + } + } + } + + if (changed) { + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + } public void setDelayUntilTime(Account account, String providerName, long delayUntil) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName diff --git a/core/java/android/hardware/Usb.java b/core/java/android/hardware/UsbManager.java index 57271d4..18790d2 100644 --- a/core/java/android/hardware/Usb.java +++ b/core/java/android/hardware/UsbManager.java @@ -17,38 +17,22 @@ package android.hardware; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + /** * Class for accessing USB state information. * @hide */ -public class Usb { - /** - * Broadcast Action: A broadcast for USB connected events. - * - * The extras bundle will name/value pairs with the name of the function - * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}. - * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE}, - * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}. - */ - public static final String ACTION_USB_CONNECTED = - "android.hardware.action.USB_CONNECTED"; - - /** - * Broadcast Action: A broadcast for USB disconnected events. - */ - public static final String ACTION_USB_DISCONNECTED = - "android.hardware.action.USB_DISCONNECTED"; - +public class UsbManager { /** * Broadcast Action: A sticky broadcast for USB state change events. * - * This is a sticky broadcast for clients that are interested in both USB connect and - * disconnect events. If you are only concerned with one or the other, you can use - * {@link #ACTION_USB_CONNECTED} or {@link #ACTION_USB_DISCONNECTED} to avoid receiving - * unnecessary broadcasts. The boolean {@link #USB_CONNECTED} extra indicates whether - * USB is connected or disconnected. - * The extras bundle will also contain name/value pairs with the name of the function - * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}. + * This is a sticky broadcast for clients that includes USB connected/disconnected state, + * the USB configuration that is currently set and a bundle containing name/value pairs + * with the names of the functions and a value of either {@link #USB_FUNCTION_ENABLED} + * or {@link #USB_FUNCTION_DISABLED}. * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE}, * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}. */ @@ -62,38 +46,70 @@ public class Usb { public static final String USB_CONNECTED = "connected"; /** + * Integer extra containing currently set USB configuration. + * Used in extras for the {@link #ACTION_USB_STATE} broadcast. + */ + public static final String USB_CONFIGURATION = "configuration"; + + /** * Name of the USB mass storage USB function. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_MASS_STORAGE = "mass_storage"; /** * Name of the adb USB function. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_ADB = "adb"; /** * Name of the RNDIS ethernet USB function. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_RNDIS = "rndis"; /** * Name of the MTP USB function. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_MTP = "mtp"; /** * Value indicating that a USB function is enabled. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_ENABLED = "enabled"; /** * Value indicating that a USB function is disabled. - * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + * Used in extras for the {@link #ACTION_USB_STATE} broadcast */ public static final String USB_FUNCTION_DISABLED = "disabled"; + + private static File getFunctionEnableFile(String function) { + return new File("/sys/class/usb_composite/" + function + "/enable"); + } + + /** + * Returns true if the specified USB function is supported by the kernel. + * Note that a USB function maybe supported but disabled. + */ + public static boolean isFunctionSupported(String function) { + return getFunctionEnableFile(function).exists(); + } + + /** + * Returns true if the specified USB function is currently enabled. + */ + public static boolean isFunctionEnabled(String function) { + try { + FileInputStream stream = new FileInputStream(getFunctionEnableFile(function)); + boolean enabled = (stream.read() == '1'); + stream.close(); + return enabled; + } catch (IOException e) { + return false; + } + } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 622bcdb..8c56fda 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -702,4 +702,28 @@ public final class NfcAdapter { return null; } } + + /** + * To change the Secure Element Card Emulation state (ON/OFF) + * @hide + */ + public void changeNfcSecureElementCardEmulationState(boolean state) + { + int seId = 11259375; + if(state){ + /* Enable card emulation */ + try { + sService.selectSecureElement(seId); + } catch (RemoteException e) { + Log.e(TAG, "Enable card emulation failed", e); + } + }else{ + /* Disable card emulation */ + try { + sService.deselectSecureElement(); + } catch (RemoteException e) { + Log.e(TAG, " card emulation failed", e); + } + } + } } diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java index 6727d6a..e4daa57 100644 --- a/core/java/android/nfc/tech/Ndef.java +++ b/core/java/android/nfc/tech/Ndef.java @@ -103,6 +103,8 @@ public final class Ndef extends BasicTagTechnology { public static final int TYPE_4 = 4; /** @hide */ public static final int TYPE_MIFARE_CLASSIC = 101; + /** @hide */ + public static final int TYPE_ICODE_SLI = 102; /** @hide */ public static final String UNKNOWN = "android.ndef.unknown"; @@ -117,6 +119,11 @@ public final class Ndef extends BasicTagTechnology { public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4"; /** NDEF on MIFARE Classic */ public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic"; + /** + * NDEF on iCODE SLI + * @hide + */ + public static final String ICODE_SLI = "com.nxp.ndef.icodesli"; private final int mMaxNdefSize; private final int mCardState; @@ -200,6 +207,8 @@ public final class Ndef extends BasicTagTechnology { return NFC_FORUM_TYPE_4; case TYPE_MIFARE_CLASSIC: return MIFARE_CLASSIC; + case TYPE_ICODE_SLI: + return ICODE_SLI; default: return UNKNOWN; } diff --git a/core/java/android/nfc/tech/package.html b/core/java/android/nfc/tech/package.html new file mode 100644 index 0000000..a99828f --- /dev/null +++ b/core/java/android/nfc/tech/package.html @@ -0,0 +1,13 @@ +<HTML> +<BODY> +<p> +These classes provide access to a tag technology's features, which vary by the type +of tag that is scanned. A scanned tag can support multiple technologies, and you can find +out what they are by calling {@link android.nfc.Tag#getTechList getTechList()}.</p> + +<p>For more information on dealing with tag technologies and handling the ones that you care about, see +<a href="{@docRoot}guide/topics/nfc/index.html#dispatch">The Tag Dispatch System</a>. +The {@link android.nfc.tech.TagTechnology} interface provides an overview of the +supported technologies.</p> +</BODY> +</HTML> diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e4485d1..5de76ff 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -132,6 +132,7 @@ public abstract class BatteryStats implements Parcelable { private static final String NETWORK_DATA = "nt"; private static final String USER_ACTIVITY_DATA = "ua"; private static final String BATTERY_DATA = "bt"; + private static final String BATTERY_DISCHARGE_DATA = "dc"; private static final String BATTERY_LEVEL_DATA = "lv"; private static final String WIFI_LOCK_DATA = "wfl"; private static final String MISC_DATA = "m"; @@ -801,6 +802,30 @@ public abstract class BatteryStats implements Parcelable { public abstract int getHighDischargeAmountSinceCharge(); /** + * Get the amount the battery has discharged while the screen was on, + * since the last time power was unplugged. + */ + public abstract int getDischargeAmountScreenOn(); + + /** + * Get the amount the battery has discharged while the screen was on, + * since the last time the device was charged. + */ + public abstract int getDischargeAmountScreenOnSinceCharge(); + + /** + * Get the amount the battery has discharged while the screen was off, + * since the last time power was unplugged. + */ + public abstract int getDischargeAmountScreenOff(); + + /** + * Get the amount the battery has discharged while the screen was off, + * since the last time the device was charged. + */ + public abstract int getDischargeAmountScreenOffSinceCharge(); + + /** * Returns the total, last, or current battery uptime in microseconds. * * @param curTime the elapsed realtime in microseconds. @@ -1095,6 +1120,17 @@ public abstract class BatteryStats implements Parcelable { getDischargeCurrentLevel()); } + if (which == STATS_SINCE_UNPLUGGED) { + dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA, + getDischargeStartLevel()-getDischargeCurrentLevel(), + getDischargeStartLevel()-getDischargeCurrentLevel(), + getDischargeAmountScreenOn(), getDischargeAmountScreenOff()); + } else { + dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA, + getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(), + getDischargeAmountScreenOn(), getDischargeAmountScreenOff()); + } + if (reqUid < 0) { Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats(); if (kernelWakelocks.size() > 0) { @@ -1451,6 +1487,10 @@ public abstract class BatteryStats implements Parcelable { pw.print(prefix); pw.print(" Last discharge cycle end level: "); pw.println(getDischargeCurrentLevel()); } + pw.print(prefix); pw.print(" Amount discharged while screen on: "); + pw.println(getDischargeAmountScreenOn()); + pw.print(prefix); pw.print(" Amount discharged while screen off: "); + pw.println(getDischargeAmountScreenOff()); pw.println(" "); } else { pw.print(prefix); pw.println(" Device battery use since last full charge"); @@ -1458,6 +1498,10 @@ public abstract class BatteryStats implements Parcelable { pw.println(getLowDischargeAmountSinceCharge()); pw.print(prefix); pw.print(" Amount discharged (upper bound): "); pw.println(getHighDischargeAmountSinceCharge()); + pw.print(prefix); pw.print(" Amount discharged while screen on: "); + pw.println(getDischargeAmountScreenOnSinceCharge()); + pw.print(prefix); pw.print(" Amount discharged while screen off: "); + pw.println(getDischargeAmountScreenOffSinceCharge()); pw.println(" "); } diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java index cc0f45e..9c4eaf4 100644 --- a/core/java/android/pim/ICalendar.java +++ b/core/java/android/pim/ICalendar.java @@ -578,6 +578,23 @@ public class ICalendar { + text); } parameter.name = text.substring(startIndex + 1, equalIndex); + } else if (c == '"') { + if (parameter == null) { + throw new FormatException("Expected parameter before '\"' in " + text); + } + if (equalIndex == -1) { + throw new FormatException("Expected '=' within parameter in " + text); + } + if (state.index > equalIndex + 1) { + throw new FormatException("Parameter value cannot contain a '\"' in " + text); + } + final int endQuote = text.indexOf('"', state.index + 1); + if (endQuote < 0) { + throw new FormatException("Expected closing '\"' in " + text); + } + parameter.value = text.substring(state.index + 1, endQuote); + state.index = endQuote + 1; + return parameter; } ++state.index; } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index a371290..595b487 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; @@ -33,6 +34,7 @@ import com.android.internal.R; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Locale; /** * A view for selecting a month / year / day based on a calendar like layout. @@ -47,7 +49,10 @@ public class DatePicker extends FrameLayout { private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; - + + // This ignores Undecimber, but we only support real Gregorian calendars. + private static final int NUMBER_OF_MONTHS = 12; + /* UI Components */ private final NumberPicker mDayPicker; private final NumberPicker mMonthPicker; @@ -62,6 +67,10 @@ public class DatePicker extends FrameLayout { private int mMonth; private int mYear; + private Object mMonthUpdateLock = new Object(); + private volatile Locale mMonthLocale; + private String[] mShortMonths; + /** * The callback used to indicate the user changes the date. */ @@ -102,8 +111,7 @@ public class DatePicker extends FrameLayout { }); mMonthPicker = (NumberPicker) findViewById(R.id.month); mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); - DateFormatSymbols dfs = new DateFormatSymbols(); - String[] months = dfs.getShortMonths(); + final String[] months = getShortMonths(); /* * If the user is in a locale where the month names are numeric, @@ -114,9 +122,9 @@ public class DatePicker extends FrameLayout { for (int i = 0; i < months.length; i++) { months[i] = String.valueOf(i + 1); } - mMonthPicker.setRange(1, 12); + mMonthPicker.setRange(1, NUMBER_OF_MONTHS); } else { - mMonthPicker.setRange(1, 12, months); + mMonthPicker.setRange(1, NUMBER_OF_MONTHS, months); } mMonthPicker.setSpeed(200); @@ -246,11 +254,30 @@ public class DatePicker extends FrameLayout { mMonth = monthOfYear; mDay = dayOfMonth; updateSpinners(); - reorderPickers(new DateFormatSymbols().getShortMonths()); + reorderPickers(getShortMonths()); notifyDateChanged(); } } + private String[] getShortMonths() { + final Locale currentLocale = Locale.getDefault(); + if (currentLocale.equals(mMonthLocale) && mShortMonths != null) { + return mShortMonths; + } else { + synchronized (mMonthUpdateLock) { + if (!currentLocale.equals(mMonthLocale)) { + mShortMonths = new String[NUMBER_OF_MONTHS]; + for (int i = 0; i < NUMBER_OF_MONTHS; i++) { + mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, + DateUtils.LENGTH_MEDIUM); + } + mMonthLocale = currentLocale; + } + } + return mShortMonths; + } + } + private static class SavedState extends BaseSavedState { private final int mYear; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 9ec9610..1339b44 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -68,7 +68,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 52; + private static final int VERSION = 53; // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -235,6 +235,12 @@ public final class BatteryStatsImpl extends BatteryStats { int mDischargeCurrentLevel; int mLowDischargeAmountSinceCharge; int mHighDischargeAmountSinceCharge; + int mDischargeScreenOnUnplugLevel; + int mDischargeScreenOffUnplugLevel; + int mDischargeAmountScreenOn; + int mDischargeAmountScreenOnSinceCharge; + int mDischargeAmountScreenOff; + int mDischargeAmountScreenOffSinceCharge; long mLastWriteTime = 0; // Milliseconds @@ -1567,6 +1573,11 @@ public final class BatteryStatsImpl extends BatteryStats { // Fake a wake lock, so we consider the device waked as long // as the screen is on. noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); + + // Update discharge amounts. + if (mOnBatteryInternal) { + updateDischargeScreenLevels(false, true); + } } } @@ -1583,6 +1594,11 @@ public final class BatteryStatsImpl extends BatteryStats { } noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); + + // Update discharge amounts. + if (mOnBatteryInternal) { + updateDischargeScreenLevels(true, false); + } } } @@ -3899,8 +3915,7 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeStartLevel = 0; mDischargeUnplugLevel = 0; mDischargeCurrentLevel = 0; - mLowDischargeAmountSinceCharge = 0; - mHighDischargeAmountSinceCharge = 0; + initDischarge(); } public BatteryStatsImpl(Parcel p) { @@ -3970,6 +3985,15 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); } + + void initDischarge() { + mLowDischargeAmountSinceCharge = 0; + mHighDischargeAmountSinceCharge = 0; + mDischargeAmountScreenOn = 0; + mDischargeAmountScreenOnSinceCharge = 0; + mDischargeAmountScreenOff = 0; + mDischargeAmountScreenOffSinceCharge = 0; + } public void resetAllStatsLocked() { mStartCount = 0; @@ -4007,11 +4031,33 @@ public final class BatteryStatsImpl extends BatteryStats { mKernelWakelockStats.clear(); } - mLowDischargeAmountSinceCharge = 0; - mHighDischargeAmountSinceCharge = 0; + initDischarge(); clearHistoryLocked(); } + + void updateDischargeScreenLevels(boolean oldScreenOn, boolean newScreenOn) { + if (oldScreenOn) { + int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel; + if (diff > 0) { + mDischargeAmountScreenOn += diff; + mDischargeAmountScreenOnSinceCharge += diff; + } + } else { + int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel; + if (diff > 0) { + mDischargeAmountScreenOff += diff; + mDischargeAmountScreenOffSinceCharge += diff; + } + } + if (newScreenOn) { + mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel; + mDischargeScreenOffUnplugLevel = 0; + } else { + mDischargeScreenOnUnplugLevel = 0; + mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel; + } + } void setOnBattery(boolean onBattery, int oldStatus, int level) { synchronized(this) { @@ -4047,6 +4093,15 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime); mDischargeCurrentLevel = mDischargeUnplugLevel = level; + if (mScreenOn) { + mDischargeScreenOnUnplugLevel = level; + mDischargeScreenOffUnplugLevel = 0; + } else { + mDischargeScreenOnUnplugLevel = 0; + mDischargeScreenOffUnplugLevel = level; + } + mDischargeAmountScreenOn = 0; + mDischargeAmountScreenOff = 0; doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); } else { updateKernelWakelocksLocked(); @@ -4062,6 +4117,7 @@ public final class BatteryStatsImpl extends BatteryStats { mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level; } + updateDischargeScreenLevels(mScreenOn, mScreenOn); doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); } if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) { @@ -4350,6 +4406,50 @@ public final class BatteryStatsImpl extends BatteryStats { return val; } } + + public int getDischargeAmountScreenOn() { + synchronized(this) { + int val = mDischargeAmountScreenOn; + if (mOnBattery && mScreenOn + && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) { + val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel; + } + return val; + } + } + + public int getDischargeAmountScreenOnSinceCharge() { + synchronized(this) { + int val = mDischargeAmountScreenOnSinceCharge; + if (mOnBattery && mScreenOn + && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) { + val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel; + } + return val; + } + } + + public int getDischargeAmountScreenOff() { + synchronized(this) { + int val = mDischargeAmountScreenOff; + if (mOnBattery && !mScreenOn + && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) { + val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel; + } + return val; + } + } + + public int getDischargeAmountScreenOffSinceCharge() { + synchronized(this) { + int val = mDischargeAmountScreenOffSinceCharge; + if (mOnBattery && !mScreenOn + && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) { + val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel; + } + return val; + } + } @Override public int getCpuSpeedSteps() { @@ -4656,7 +4756,9 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeCurrentLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); - + mDischargeAmountScreenOnSinceCharge = in.readInt(); + mDischargeAmountScreenOffSinceCharge = in.readInt(); + mStartCount++; mScreenOn = false; @@ -4851,6 +4953,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mDischargeCurrentLevel); out.writeInt(getLowDischargeAmountSinceCharge()); out.writeInt(getHighDischargeAmountSinceCharge()); + out.writeInt(getDischargeAmountScreenOnSinceCharge()); + out.writeInt(getDischargeAmountScreenOffSinceCharge()); mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { @@ -5090,6 +5194,10 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeCurrentLevel = in.readInt(); mLowDischargeAmountSinceCharge = in.readInt(); mHighDischargeAmountSinceCharge = in.readInt(); + mDischargeAmountScreenOn = in.readInt(); + mDischargeAmountScreenOnSinceCharge = in.readInt(); + mDischargeAmountScreenOff = in.readInt(); + mDischargeAmountScreenOffSinceCharge = in.readInt(); mLastWriteTime = in.readLong(); mMobileDataRx[STATS_LAST] = in.readLong(); @@ -5191,6 +5299,10 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mDischargeCurrentLevel); out.writeInt(mLowDischargeAmountSinceCharge); out.writeInt(mHighDischargeAmountSinceCharge); + out.writeInt(mDischargeAmountScreenOn); + out.writeInt(mDischargeAmountScreenOnSinceCharge); + out.writeInt(mDischargeAmountScreenOff); + out.writeInt(mDischargeAmountScreenOffSinceCharge); out.writeLong(mLastWriteTime); out.writeLong(getMobileTcpBytesReceived(STATS_SINCE_UNPLUGGED)); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index f0e5517..060f858 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -511,7 +511,7 @@ public class ZygoteInit { String args[] = { "--setuid=1000", "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a130bf5..e3323b3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -339,7 +339,7 @@ android:description="@string/permdesc_bluetooth" android:label="@string/permlab_bluetooth" /> - <!-- Allows applications to directly communicate over NFC --> + <!-- Allows applications to perform I/O operations over NFC --> <permission android:name="android.permission.NFC" android:permissionGroup="android.permission-group.NETWORK" android:protectionLevel="dangerous" diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 48c73cc..b15764c 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -270,7 +270,7 @@ <string name="permlab_readLogs" msgid="6615778543198967614">"機密ログデータの読み取り"</string> <string name="permdesc_readLogs" msgid="8896449437464867766">"システムの各種ログファイルの読み取りをアプリケーションに許可します。許可すると端末の使用状況に関する全般的な情報が読み取られます。この情報には個人情報や機密情報が含まれる場合があります。"</string> <string name="permlab_diagnostic" msgid="8076743953908000342">"diagが所有するリソースの読み書き"</string> - <string name="permdesc_diagnostic" msgid="3121238373951637049">"diagグループが所有するリソース(例:/dev内のファイル)への読み書きをアプリケーションに許可します。システムの安定性とセキュリティに影響する恐れがあります。メーカー/オペレーターによるハードウェア固有の診断以外には使用しないでください。"</string> + <string name="permdesc_diagnostic" msgid="3121238373951637049">"diagグループが所有するリソース(例:/dev内のファイル)への読み書きをアプリケーションに許可します。システムの安定性とセキュリティに影響する恐れがあります。メーカー/通信事業者によるハードウェア固有の診断以外には使用しないでください。"</string> <string name="permlab_changeComponentState" msgid="79425198834329406">"アプリケーションのコンポーネントを有効/無効にする"</string> <string name="permdesc_changeComponentState" msgid="4569107043246700630">"別アプリケーションのコンポーネントの有効/無効を変更することをアプリケーションに許可します。これにより悪意のあるアプリケーションが、携帯電話の重要な機能を無効にする恐れがあります。アプリケーションコンポーネントが利用できない、整合性が取れない、または不安定な状態になる恐れがあるので、許可には注意が必要です。"</string> <string name="permlab_setPreferredApplications" msgid="3393305202145172005">"優先アプリケーションの設定"</string> @@ -352,13 +352,13 @@ <string name="permlab_bindGadget" msgid="776905339015863471">"ウィジェットの選択"</string> <string name="permdesc_bindGadget" msgid="2098697834497452046">"どのアプリケーションがどのウィジェットを使用できるかシステムに指定することをこのアプリケーションに許可します。これにより、アプリケーション間で個人データにアクセスできるようになります。通常のアプリケーションでは使用しません。"</string> <string name="permlab_modifyPhoneState" msgid="8423923777659292228">"端末ステータスの変更"</string> - <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"端末の電話機能のコントロールをアプリケーションに許可します。アプリケーションは、ネットワークの切り替え、携帯電話の無線通信のオン/オフなどを通知せずに行うことができます。"</string> + <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"端末の電話機能のコントロールをアプリケーションに許可します。アプリケーションは、ネットワークの切り替え、携帯電話の無線通信のON/OFFなどを通知せずに行うことができます。"</string> <string name="permlab_readPhoneState" msgid="2326172951448691631">"携帯のステータスとIDの読み取り"</string> <string name="permdesc_readPhoneState" msgid="188877305147626781">"端末の電話機能へのアクセスをアプリケーションに許可します。この権限が許可されたアプリケーションでは、この携帯の電話番号やシリアル番号、通話中かどうか、通話相手の電話番号などを特定できます。"</string> <string name="permlab_wakeLock" msgid="573480187941496130">"端末のスリープを無効にする"</string> <string name="permdesc_wakeLock" msgid="7584036471227467099">"端末のスリープを無効にすることをアプリケーションに許可します。"</string> <string name="permlab_devicePower" msgid="4928622470980943206">"電源のON/OFF"</string> - <string name="permdesc_devicePower" msgid="4577331933252444818">"携帯電話の電源のオン/オフをアプリケーションに許可します。"</string> + <string name="permdesc_devicePower" msgid="4577331933252444818">"携帯電話の電源のON/OFFをアプリケーションに許可します。"</string> <string name="permlab_factoryTest" msgid="3715225492696416187">"出荷時試験モードでの実行"</string> <string name="permdesc_factoryTest" msgid="8136644990319244802">"携帯電話のハードウェアへのアクセスを完全に許可して、低レベルのメーカーテストとして実行します。メーカーのテストモードで携帯電話を使用するときのみ利用できます。"</string> <string name="permlab_setWallpaper" msgid="6627192333373465143">"壁紙の設定"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 179dabe..dbb76ff 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -177,7 +177,7 @@ <string name="permdesc_statusBarService" msgid="4097605867643520920">"애플리케이션이 상태 표시줄이 되도록 허용합니다."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"상태 표시줄 확장/축소"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"애플리케이션이 상태 표시줄을 확장하거나 축소할 수 있도록 합니다."</string> - <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"발신전화 가로채기"</string> + <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"발신전화 차단"</string> <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"애플리케이션이 발신전화를 처리하고 전화를 걸 번호를 변경할 수 있도록 합니다. 이 경우 악성 애플리케이션이 발신전화를 모니터링하거나, 다른 방향으로 돌리거나, 중단시킬 수 있습니다."</string> <string name="permlab_receiveSms" msgid="2697628268086208535">"SMS 수신"</string> <string name="permdesc_receiveSms" msgid="6298292335965966117">"애플리케이션이 SMS 메시지를 받고 처리할 수 있도록 합니다. 이 경우 악성 애플리케이션이 메시지를 모니터링하거나 사용자가 보기 전에 삭제할 수 있습니다."</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index de72e8c..0968c04 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -50,7 +50,7 @@ <string name="needPuk2" msgid="4526033371987193070">"Wprowadź kod PUK2, aby odblokować kartę SIM."</string> <string name="ClipMmi" msgid="6952821216480289285">"Identyfikator rozmówcy przy połączeniach przychodzących"</string> <string name="ClirMmi" msgid="7784673673446833091">"Identyfikator rozmówcy przy połączeniach wychodzących"</string> - <string name="CfMmi" msgid="5123218989141573515">"Przekierowania połączeń"</string> + <string name="CfMmi" msgid="5123218989141573515">"Przekazywanie połączeń"</string> <string name="CwMmi" msgid="9129678056795016867">"Połączenia oczekujące"</string> <string name="BaMmi" msgid="455193067926770581">"Blokada dzwonienia"</string> <string name="PwdMmi" msgid="7043715687905254199">"Zmiana hasła"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index dc095c5..3568292 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -547,7 +547,7 @@ <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Экран заблокирован."</string> <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Нажмите \"Меню\", чтобы разблокировать экран или вызвать службу экстренной помощи."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Для разблокировки нажмите \"Меню\"."</string> - <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Для разблокировки введите графический ключ"</string> + <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Введите графический ключ"</string> <string name="lockscreen_emergency_call" msgid="5347633784401285225">"Экстренный вызов"</string> <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Вернуться к вызову"</string> <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Правильно!"</string> @@ -874,17 +874,17 @@ <string name="sync_binding_label" msgid="3687969138375092423">"Синхр."</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Спец. возможности"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Фоновый рисунок"</string> - <string name="chooser_wallpaper" msgid="7873476199295190279">"Изменить фоновый рисунок"</string> + <string name="chooser_wallpaper" msgid="7873476199295190279">"Сменить обои"</string> <string name="pptp_vpn_description" msgid="2688045385181439401">"Протокол PPTP"</string> <string name="l2tp_vpn_description" msgid="3750692169378923304">"Протокол L2TP"</string> - <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на основе предв. общ. ключа)"</string> - <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на основе сертификата)"</string> + <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN с использованием общего ключа"</string> + <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN с использованием сертификатов"</string> <string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string> <string name="reset" msgid="2448168080964209908">"Сбросить"</string> <string name="submit" msgid="1602335572089911941">"Отправить"</string> <string name="description_star" msgid="2654319874908576133">"избранное"</string> - <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Режим громкой связи включен"</string> - <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Выберите для выхода из режима громкой связи."</string> + <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Включен режим \"Штурман\""</string> + <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Чтобы закрыть приложение, нажмите здесь."</string> <string name="tethered_notification_title" msgid="3146694234398202601">"USB-модем или точка доступа Wi-Fi активны"</string> <string name="tethered_notification_message" msgid="3067108323903048927">"Нажмите для настройки"</string> <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Активная передача данных"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 78fe64b..da8e69e 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -191,11 +191,11 @@ <string name="permdesc_writeSms" msgid="6299398896177548095">"Tillåter att program skriver till SMS-meddelanden som lagrats på din telefon eller SIM-kort. Skadliga program kan radera dina meddelanden."</string> <string name="permlab_receiveWapPush" msgid="8258226427716551388">"ta emot WAP"</string> <string name="permdesc_receiveWapPush" msgid="5979623826128082171">"Tillåter att program tar emot och bearbetar WAP-meddelanden. Skadliga program kan övervaka dina meddelanden eller ta bort dem utan att visa dem för dig."</string> - <string name="permlab_getTasks" msgid="5005277531132573353">"hämta program som körs"</string> + <string name="permlab_getTasks" msgid="5005277531132573353">"hämta appar som körs"</string> <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillåter att program hämtar information om uppgifter som körs och har körts. Skadliga program kan upptäcka privat information om andra program."</string> <string name="permlab_reorderTasks" msgid="5669588525059921549">"byt ordning på program som körs"</string> <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillåter att ett program flyttar uppgifter till förgrunden eller bakgrunden. Skadliga program kan tvinga sig till förgrunden utan att du kan styra det."</string> - <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktivera felsökning av program"</string> + <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktivera felsökning av appar"</string> <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillåter att ett program aktiverar felsökning för ett annat program. Skadliga program kan använda detta för att avsluta andra program."</string> <string name="permlab_changeConfiguration" msgid="8214475779521218295">"ändra dina gränssnittsinställningar"</string> <string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillåter att ett program ändrar den aktuella konfigurationen, till exempel språk eller övergripande teckenformat."</string> @@ -249,11 +249,11 @@ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Tillåter att innehavaren skickar avsikter till en enhetsadministratör. Vanliga program behöver aldrig göra detta."</string> <string name="permlab_setOrientation" msgid="3365947717163866844">"ändra bildskärmens rikting"</string> <string name="permdesc_setOrientation" msgid="6335814461615851863">"Tillåter att ett program när som helst ändrar skärmens rotering. Behövs inte för vanliga program."</string> - <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"skicka Linux-signaler till program"</string> + <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"skicka Linux-signaler till appar"</string> <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"Tillåter att programmet begär att den angivna signalen skickas till alla beständiga processer."</string> <string name="permlab_persistentActivity" msgid="8659652042401085862">"se till att programmet alltid körs"</string> <string name="permdesc_persistentActivity" msgid="5037199778265006008">"Tillåter att ett program gör vissa delar beständiga så att systemet inte kan använda det för andra program."</string> - <string name="permlab_deletePackages" msgid="3343439331576348805">"ta bort program"</string> + <string name="permlab_deletePackages" msgid="3343439331576348805">"ta bort appar"</string> <string name="permdesc_deletePackages" msgid="3634943677518723314">"Tillåter att ett program tar bort Android-paket. Skadliga program kan använda detta för att ta bort viktiga program."</string> <string name="permlab_clearAppUserData" msgid="2192134353540277878">"ta bort de andra programmens uppgifter"</string> <string name="permdesc_clearAppUserData" msgid="7546345080434325456">"Tillåter att ett program tar bort användardata."</string> @@ -263,7 +263,7 @@ <string name="permdesc_getPackageSize" msgid="5557253039670753437">"Tillåter att ett program hämtar kod, data och cachestorlekar"</string> <string name="permlab_installPackages" msgid="335800214119051089">"installera program direkt"</string> <string name="permdesc_installPackages" msgid="526669220850066132">"Tillåter att ett program installerar nya eller uppdaterade Android-paket. Skadliga program kan använda detta för att lägga till nya program med godtyckliga och starka behörigheter."</string> - <string name="permlab_clearAppCache" msgid="4747698311163766540">"ta bort cacheinformation för alla program"</string> + <string name="permlab_clearAppCache" msgid="4747698311163766540">"ta bort cacheinformation för alla appar"</string> <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillåter att ett program frigör lagringsutrymme i telefonen genom att ta bort filer i programmets katalog för cachelagring. Åtkomst är mycket begränsad, vanligtvis till systemprocesser."</string> <string name="permlab_movePackage" msgid="728454979946503926">"Flytta programresurser"</string> <string name="permdesc_movePackage" msgid="6323049291923925277">"Tillåter att ett program flyttar programresurser från interna till externa medier och tvärt om."</string> @@ -782,7 +782,7 @@ <item quantity="other" msgid="7915895323644292768">"Öppna Wi-Fi-nätverk är tillgängliga"</item> </plurals> <string name="select_character" msgid="3365550120617701745">"Infoga tecken"</string> - <string name="sms_control_default_app_name" msgid="7630529934366549163">"Okänt program"</string> + <string name="sms_control_default_app_name" msgid="7630529934366549163">"Okänd app"</string> <string name="sms_control_title" msgid="7296612781128917719">"Skickar SMS"</string> <string name="sms_control_message" msgid="1289331457999236205">"Flera SMS-meddelanden skickas. Tryck på OK om du vill fortsätta eller på Avbryt om du vill avsluta sändningen."</string> <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 355e681..c06c076 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -399,4 +399,7 @@ <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD --> <string-array name="config_twoDigitNumberPattern"> </string-array> + + <!-- The VoiceMail default value is displayed to my own number if it is true --> + <bool name="config_telephony_use_own_number_for_voicemail">false</bool> </resources> diff --git a/docs/html/guide/topics/nfc/index.jd b/docs/html/guide/topics/nfc/index.jd index 3992099..c4917b4 100644 --- a/docs/html/guide/topics/nfc/index.jd +++ b/docs/html/guide/topics/nfc/index.jd @@ -31,29 +31,33 @@ page.title=Near Field Communication </div> </div> - <p>Near Field Communication (NFC) is a set of short-range wireless technologies, similar to RFID. - It typically requires a distance of 4 cm or less and operates at 13.56mhz and at rates ranging - from 106 kbit/s to 848 kbit/s. NFC communication always involves an initiator and a target. The - initiator actively generates an RF field that can power a passive target. This enables NFC - targets to take very simple form factors such as tags, stickers or cards that do not require - power. NFC peer-to-peer communication is also possible, where both devices are powered.</p> - - <p>Compared to other wireless technologies such as Bluetooth or WiFi, NFC provides much lower - bandwidth and range, but provides low-cost, un-powered targets and do not require discovery or - pairing. Users interact with NFC tags with just a tap. Targets can range in complexity. Simple - tags just offer read and write capabilities, sometimes with one-time programmable areas to make - the card read-only. More complex tags offer math operations, and have cryptographic hardware to - authenticate access to a sector. The most sophisticated tags contain operating environments, - allowing complex interactions with applets that are running on the tag.</p> - - <p>An Android device with NFC hardware typically acts as an initiator. This mode is also known as - NFC reader/writer. The device actively looks for NFC tags and starts activities to handle them in - this mode. In Android 2.3.3, devices also have some limited peer-to-peer support.</p> + <p>Near Field Communication (NFC) is a set of short-range wireless technologies, typically + requiring a distance of 4cm or less. NFC operates at 13.56mhz, and at rates ranging + from 106 kbit/s to 848 kbit/s. NFC communication always involves an initiator and a target. + The initiator actively generates an RF field that can power a passive target. This + enables NFC targets to take very simple form factors such as tags, stickers or cards that do + not require power. NFC peer-to-peer communication is also possible, where both devices + are powered. + <p> + Compared to other wireless technologies such as Bluetooth or WiFi, NFC provides much lower + bandwidth and range, but enables low-cost, un-powered targets + and does not require discovery or pairing. Interactions can be initiated with just a tap. + <p> + An Android device with NFC hardware will typically act as an initiator when the screen is + on. This mode is also known as NFC reader/writer. It will actively look for NFC tags and start + activities to handle them. Android 2.3.3 also has some limited P2P support. + <p> + Tags can range in complexity, simple tags just offer read/write semantics, sometimes + with one-time-programmable areas to make the card read-only. More complex tags offer + math operations, and have cryptographic hardware to authenticate access to a sector. + The most sophisticated tags contain operating environments, allowing + complex interactions with code executing on the tag. <h2 id="api">API Overview</h2> - <p>The {@link android.nfc} package contain the high-level classes to interact with the local - device's NFC adapter, to represent discovered tags, and to use the NDEF data format.</p> + <p>The {@link android.nfc} package contains the high-level classes to interact + with the local device's NFC adapter, to represent discovered tags, and to use + the NDEF data format. <table> <tr> @@ -65,44 +69,52 @@ page.title=Near Field Communication <tr> <td>{@link android.nfc.NfcManager}</td> - <td>A high level manager class that enumerates the NFC adapters on this Android device. Since - most Android devices only have one NFC adapter, you can just use the static helper {@link - android.nfc.NfcAdapter#getDefaultAdapter()} for most situations.</td> + + <td>A high level manager class that enumerates the NFC adapters on this Android device. + Since most Android devices only have one NFC adapter, you can just use the static helper + {@link android.nfc.NfcAdapter#getDefaultAdapter(Context)} for most situations.</td> </tr> <tr> <td>{@link android.nfc.NfcAdapter}</td> - <td>Represents the local NFC adapter and defines the Intents that are used in the tag - dispatch system. It provides methods to register for foreground tag dispatching and - foreground NDEF pushing. Foreground NDEF push is the only peer-to-peer support that is - currently provided in Android.</td> + <td>Represents the local NFC adapter. Defines the intent's used to request + tag dispatch to your activity, and provides methods to register for foreground + tag dispatch and foreground NDEF push. Foreground NDEF push is the only + peer-to-peer support that is currently provided in Android.</td> </tr> <tr> <td>{@link android.nfc.NdefMessage} and {@link android.nfc.NdefRecord}</td> - <td>NDEF is an NFC Forum defined data structure, designed to efficiently store data on NFC - tags, such as Text, URLs, and other MIME types. An {@link android.nfc.NdefMessage} acts as a + <td>NDEF is an NFC Forum defined data structure, designed to efficiently + store data on NFC tags, such as text, URL's, and other MIME types. A + {@link android.nfc.NdefMessage} acts as a container for the data that you want to transmit or read. One {@link android.nfc.NdefMessage} - object contains zero or more {@link android.nfc.NdefRecord}s. Each NDEF Record has a type - such as Text, URL, Smart Poster, or any MIME type. The type of the first NDEF Record in the - NDEF message is used to dispatch a tag to an Activity.</td> + object contains zero or more {@link android.nfc.NdefRecord}s. Each NDEF record + has a type such as text, URL, smart poster, or any MIME data. The type of the + first NDEF record in the NDEF message is used to dispatch a tag to an activity + on Android.</td> </tr> <tr> <td>{@link android.nfc.Tag}</td> - <td>Represents a passive NFC target. These can come in many form factors such as a tag, card, - FOB, or an even more complex device doing card emulation. When a tag is discovered, a {@link - android.nfc.Tag} object is created and wrapped inside an Intent. The dispatch system sends - the Intent to a compatible Activity <code>startActivity()</code>. You can use the {@link + <td>Represents a passive NFC target. These can come in many form factors such as + a tag, card, key fob, or even a phone doing card emulation. When a tag is + discovered, a {@link android.nfc.Tag} object is created and wrapped inside an + Intent. The NFC dispatch system sends the intent to a compatible actvitiy + using <code>startActivity()</code>. You can use the {@link android.nfc.Tag#getTechList getTechList()} method to determine the technologies supported by this tag and create the corresponding {@link android.nfc.tech.TagTechnology} object with one of classes provided by {@link android.nfc.tech}.</td> </tr> </table> + <p>The {@link android.nfc.tech} package contains classes to query properties + and perform I/O operations on a tag. The classes are divided to represent different + NFC technologies that can be available on a tag. + <p>The {@link android.nfc.tech} package contains classes to query properties and perform I/O operations on a tag. The classes are divided to represent different NFC technologies that can be available on a Tag:</p> @@ -117,7 +129,7 @@ page.title=Near Field Communication <tr> <td>{@link android.nfc.tech.TagTechnology}</td> - <td>The interface that all Tag Technology classes must implement.</td> + <td>The interface that all tag technology classes must implement.</td> </tr> <tr> @@ -153,8 +165,8 @@ page.title=Near Field Communication <tr> <td>{@link android.nfc.tech.Ndef}</td> - <td>Provides access to NDEF data and operations on NFC Tags that have been formatted as - NDEF.</td> + <td>Provides access to NDEF data and operations on NFC tags that have been formatted as NDEF. + </td> </tr> <tr> @@ -166,15 +178,15 @@ page.title=Near Field Communication <tr> <td>{@link android.nfc.tech.MifareClassic}</td> - <td>Provides access to MIFARE Classic properties and I/O operations. Not all Android devices - provide implementations for this class.</td> + <td>Provides access to MIFARE Classic properties and I/O operations, if this + Android device supports MIFARE.</td> </tr> <tr> <td>{@link android.nfc.tech.MifareUltralight}</td> - <td>Provides access to MIFARE Ultralight properties and I/O operations. Not all Android - devices provide implementations for this class.</td> + <td>Provides access to MIFARE Ultralight properties and I/O operations, if this + Android device supports MIFARE.</td> </tr> </table> @@ -191,12 +203,13 @@ page.title=Near Field Communication </li> <li>The minimum SDK version that your application can support. API level 9 only supports - limited tag dispatching with {@link android.nfc.NfcAdapter#ACTION_TAG_DISCOVERED}, and only - gives access to NDEF messages via the {@link android.nfc.NfcAdapter#EXTRA_NDEF_MESSAGES} extra. - No other tag properties or I/O operations are accessible. API level 10 adds comprehensive - reader/writer support, so you probably want to use this for more functionality. - <pre class="pretty-print"> -<uses-sdk android:minSdkVersion="9|10"/> + limited tag dispatch via {@link android.nfc.NfcAdapter#ACTION_TAG_DISCOVERED}, + and only gives access to NDEF messages via the {@link android.nfc.NfcAdapter#EXTRA_NDEF_MESSAGES} + extra. No other tag properties or I/O operations are accessible. You probably want + to use API level 10 which includes comprehensive reader/writer support. + +<pre class="pretty-print"> +<uses-sdk android:minSdkVersion="10"/> </pre> </li> diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd index bfcd14e..551361f 100644 --- a/docs/html/sdk/index.jd +++ b/docs/html/sdk/index.jd @@ -3,7 +3,7 @@ sdk.redirect=0 sdk.win_installer=installer_r09-windows.exe sdk.win_installer_bytes=32828818 -sdk.win_installer_checksum=a0185701ac0d635a4fbf8169ac949a3c5b3d31e0 +sdk.win_installer_checksum=ef92e643731f820360e036eb11658656 sdk.win_download=android-sdk_r09-windows.zip sdk.win_bytes=32779808 diff --git a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp index 13988cd..9f6bd29 100644 --- a/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp +++ b/media/libstagefright/rtsp/AMPEG4ElementaryAssembler.cpp @@ -104,7 +104,7 @@ AMPEG4ElementaryAssembler::AMPEG4ElementaryAssembler( mNextExpectedSeqNoValid(false), mNextExpectedSeqNo(0), mAccessUnitDamaged(false) { - mIsGeneric = desc.startsWith("mpeg4-generic/"); + mIsGeneric = !strncasecmp(desc.c_str(),"mpeg4-generic/", 14); if (mIsGeneric) { AString value; diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp index 10cc88b..7f09248 100644 --- a/media/libstagefright/rtsp/APacketSource.cpp +++ b/media/libstagefright/rtsp/APacketSource.cpp @@ -627,7 +627,7 @@ APacketSource::APacketSource( mFormat->setInt32(kKeyWidth, width); mFormat->setInt32(kKeyHeight, height); - } else if (!strncmp(desc.c_str(), "mpeg4-generic/", 14)) { + } else if (!strncasecmp(desc.c_str(), "mpeg4-generic/", 14)) { AString val; if (!GetAttribute(params.c_str(), "mode", &val) || (strcasecmp(val.c_str(), "AAC-lbr") diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp index 5a1ea5c..72943ff 100644 --- a/media/libstagefright/rtsp/ARTPConnection.cpp +++ b/media/libstagefright/rtsp/ARTPConnection.cpp @@ -123,7 +123,7 @@ void ARTPConnection::MakePortPair( struct sockaddr_in addr; memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(*rtpSocket, @@ -346,6 +346,8 @@ void ARTPConnection::onPollStreams() { } status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) { + LOGV("receiving %s", receiveRTP ? "RTP" : "RTCP"); + CHECK(!s->mIsInjected); sp<ABuffer> buffer = new ABuffer(65536); diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp index 5aae4e7..87b5a7e 100644 --- a/media/libstagefright/rtsp/ARTPSource.cpp +++ b/media/libstagefright/rtsp/ARTPSource.cpp @@ -67,7 +67,7 @@ ARTPSource::ARTPSource( } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) { mAssembler = new AAMRAssembler(notify, true /* isWide */, params); } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8) - || !strncmp(desc.c_str(), "mpeg4-generic/", 14)) { + || !strncasecmp(desc.c_str(), "mpeg4-generic/", 14)) { mAssembler = new AMPEG4ElementaryAssembler(notify, desc, params); mIssueFIRRequests = true; } else { diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index e936923..0740515 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -545,6 +545,10 @@ sp<ABuffer> ARTSPConnection::receiveBinaryData() { return buffer; } +static bool IsRTSPVersion(const AString &s) { + return s == "RTSP/1.0"; +} + bool ARTSPConnection::receiveRTSPReponse() { AString statusLine; @@ -584,13 +588,27 @@ bool ARTSPConnection::receiveRTSPReponse() { return false; } - AString statusCodeStr( - response->mStatusLine, space1 + 1, space2 - space1 - 1); + bool isRequest = false; - if (!ParseSingleUnsignedLong( - statusCodeStr.c_str(), &response->mStatusCode) - || response->mStatusCode < 100 || response->mStatusCode > 999) { - return false; + if (!IsRTSPVersion(AString(response->mStatusLine, 0, space1))) { + CHECK(IsRTSPVersion( + AString( + response->mStatusLine, + space2 + 1, + response->mStatusLine.size() - space2 - 1))); + + isRequest = true; + + response->mStatusCode = 0; + } else { + AString statusCodeStr( + response->mStatusLine, space1 + 1, space2 - space1 - 1); + + if (!ParseSingleUnsignedLong( + statusCodeStr.c_str(), &response->mStatusCode) + || response->mStatusCode < 100 || response->mStatusCode > 999) { + return false; + } } AString line; @@ -680,7 +698,63 @@ bool ARTSPConnection::receiveRTSPReponse() { } } - return notifyResponseListener(response); + return isRequest + ? handleServerRequest(response) + : notifyResponseListener(response); +} + +bool ARTSPConnection::handleServerRequest(const sp<ARTSPResponse> &request) { + // Implementation of server->client requests is optional for all methods + // but we do need to respond, even if it's just to say that we don't + // support the method. + + ssize_t space1 = request->mStatusLine.find(" "); + CHECK_GE(space1, 0); + + AString response; + response.append("RTSP/1.0 501 Not Implemented\r\n"); + + ssize_t i = request->mHeaders.indexOfKey("cseq"); + + if (i >= 0) { + AString value = request->mHeaders.valueAt(i); + + unsigned long cseq; + if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) { + return false; + } + + response.append("CSeq: "); + response.append(cseq); + response.append("\r\n"); + } + + response.append("\r\n"); + + size_t numBytesSent = 0; + while (numBytesSent < response.size()) { + ssize_t n = + send(mSocket, response.c_str() + numBytesSent, + response.size() - numBytesSent, 0); + + if (n == 0) { + // Server closed the connection. + LOGE("Server unexpectedly closed the connection."); + + return false; + } else if (n < 0) { + if (errno == EINTR) { + continue; + } + + LOGE("Error sending rtsp response."); + return false; + } + + numBytesSent += (size_t)n; + } + + return true; } // static diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h index 19be2a6..0fecf3c 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.h +++ b/media/libstagefright/rtsp/ARTSPConnection.h @@ -109,6 +109,8 @@ private: status_t findPendingRequest( const sp<ARTSPResponse> &response, ssize_t *index) const; + bool handleServerRequest(const sp<ARTSPResponse> &request); + static bool ParseSingleUnsignedLong( const char *from, unsigned long *x); diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp index 3e710dc..f03f7a2 100644 --- a/media/libstagefright/rtsp/ASessionDescription.cpp +++ b/media/libstagefright/rtsp/ASessionDescription.cpp @@ -71,6 +71,11 @@ bool ASessionDescription::parse(const void *data, size_t size) { line.setTo(desc, i, eolPos - i); } + if (line.empty()) { + i = eolPos + 1; + continue; + } + if (line.size() < 2 || line.c_str()[1] != '=') { return false; } diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index 306a9c1..72a2fdb 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -38,6 +38,7 @@ #include <arpa/inet.h> #include <sys/socket.h> +#include <netdb.h> // If no access units are received within 3 secs, assume that the rtp // stream has ended and signal end of stream. @@ -119,9 +120,10 @@ struct MyHandler : public AHandler { // want to transmit user/pass in cleartext. AString host, path, user, pass; unsigned port; - if (ARTSPConnection::ParseURL( - mSessionURL.c_str(), &host, &port, &path, &user, &pass) - && user.size() > 0) { + CHECK(ARTSPConnection::ParseURL( + mSessionURL.c_str(), &host, &port, &path, &user, &pass)); + + if (user.size() > 0) { mSessionURL.clear(); mSessionURL.append("rtsp://"); mSessionURL.append(host); @@ -131,6 +133,8 @@ struct MyHandler : public AHandler { LOGI("rewritten session url: '%s'", mSessionURL.c_str()); } + + mSessionHost = host; } void connect(const sp<AMessage> &doneMsg) { @@ -246,34 +250,64 @@ struct MyHandler : public AHandler { // In case we're behind NAT, fire off two UDP packets to the remote // rtp/rtcp ports to poke a hole into the firewall for future incoming // packets. We're going to send an RR/SDES RTCP packet to both of them. - void pokeAHole(int rtpSocket, int rtcpSocket, const AString &transport) { + bool pokeAHole(int rtpSocket, int rtcpSocket, const AString &transport) { + struct sockaddr_in addr; + memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + addr.sin_family = AF_INET; + AString source; AString server_port; if (!GetAttribute(transport.c_str(), "source", - &source) - || !GetAttribute(transport.c_str(), + &source)) { + LOGW("Missing 'source' field in Transport response. Using " + "RTSP endpoint address."); + + struct hostent *ent = gethostbyname(mSessionHost.c_str()); + if (ent == NULL) { + LOGE("Failed to look up address of session host '%s'", + mSessionHost.c_str()); + + return false; + } + + addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; + } else { + addr.sin_addr.s_addr = inet_addr(source.c_str()); + } + + if (!GetAttribute(transport.c_str(), "server_port", &server_port)) { - return; + LOGI("Missing 'server_port' field in Transport response."); + return false; } int rtpPort, rtcpPort; if (sscanf(server_port.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2 || rtpPort <= 0 || rtpPort > 65535 || rtcpPort <=0 || rtcpPort > 65535 - || rtcpPort != rtpPort + 1 - || (rtpPort & 1) != 0) { - return; + || rtcpPort != rtpPort + 1) { + LOGE("Server picked invalid RTP/RTCP port pair %s," + " RTP port must be even, RTCP port must be one higher.", + server_port.c_str()); + + return false; } - struct sockaddr_in addr; - memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(source.c_str()); + if (rtpPort & 1) { + LOGW("Server picked an odd RTP port, it should've picked an " + "even one, we'll let it pass for now, but this may break " + "in the future."); + } if (addr.sin_addr.s_addr == INADDR_NONE) { - return; + return true; + } + + if (IN_LOOPBACK(ntohl(addr.sin_addr.s_addr))) { + // No firewalls to traverse on the loopback interface. + return true; } // Make up an RR/SDES RTCP packet. @@ -287,16 +321,26 @@ struct MyHandler : public AHandler { ssize_t n = sendto( rtpSocket, buf->data(), buf->size(), 0, (const sockaddr *)&addr, sizeof(addr)); - CHECK_EQ(n, (ssize_t)buf->size()); + + if (n < (ssize_t)buf->size()) { + LOGE("failed to poke a hole for RTP packets"); + return false; + } addr.sin_port = htons(rtcpPort); n = sendto( rtcpSocket, buf->data(), buf->size(), 0, (const sockaddr *)&addr, sizeof(addr)); - CHECK_EQ(n, (ssize_t)buf->size()); + + if (n < (ssize_t)buf->size()) { + LOGE("failed to poke a hole for RTCP packets"); + return false; + } LOGV("successfully poked holes."); + + return true; } virtual void onMessageReceived(const sp<AMessage> &msg) { @@ -379,6 +423,7 @@ struct MyHandler : public AHandler { response->mContent->size()); if (!mSessionDesc->isValid()) { + LOGE("Failed to parse session description."); result = ERROR_MALFORMED; } else { ssize_t i = response->mHeaders.indexOfKey("content-base"); @@ -393,6 +438,25 @@ struct MyHandler : public AHandler { } } + if (!mBaseURL.startsWith("rtsp://")) { + // Some misbehaving servers specify a relative + // URL in one of the locations above, combine + // it with the absolute session URL to get + // something usable... + + LOGW("Server specified a non-absolute base URL" + ", combining it with the session URL to " + "get something usable..."); + + AString tmp; + CHECK(MakeURL( + mSessionURL.c_str(), + mBaseURL.c_str(), + &tmp)); + + mBaseURL = tmp; + } + CHECK_GT(mSessionDesc->countTracks(), 1u); setupTrack(1); } @@ -453,9 +517,12 @@ struct MyHandler : public AHandler { if (!track->mUsingInterleavedTCP) { AString transport = response->mHeaders.valueAt(i); - pokeAHole(track->mRTPSocket, - track->mRTCPSocket, - transport); + // We are going to continue even if we were + // unable to poke a hole into the firewall... + pokeAHole( + track->mRTPSocket, + track->mRTCPSocket, + transport); } mRTPConn->addStream( @@ -865,10 +932,7 @@ struct MyHandler : public AHandler { case 'tiou': { if (!mReceivedFirstRTCPPacket) { - if (mTryFakeRTCP) { - LOGW("Never received any data, disconnecting."); - (new AMessage('abor', id()))->post(); - } else if (mTryTCPInterleaving && mReceivedFirstRTPPacket) { + if (mReceivedFirstRTPPacket && !mTryFakeRTCP) { LOGW("We received RTP packets but no RTCP packets, " "using fake timestamps."); @@ -876,7 +940,7 @@ struct MyHandler : public AHandler { mReceivedFirstRTCPPacket = true; mRTPConn->fakeTimestamps(); - } else { + } else if (!mReceivedFirstRTPPacket && !mTryTCPInterleaving) { LOGW("Never received any data, switching transports."); mTryTCPInterleaving = true; @@ -884,6 +948,9 @@ struct MyHandler : public AHandler { sp<AMessage> msg = new AMessage('abor', id()); msg->setInt32("reconnect", true); msg->post(); + } else { + LOGW("Never received any data, disconnecting."); + (new AMessage('abor', id()))->post(); } } break; @@ -1010,6 +1077,7 @@ private: sp<ASessionDescription> mSessionDesc; AString mOriginalSessionURL; // This one still has user:pass@ AString mSessionURL; + AString mSessionHost; AString mBaseURL; AString mSessionID; bool mSetupTracksSuccessful; diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index 6d34927..d26d13e 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -814,7 +814,7 @@ EGLBoolean eglTerminate(EGLDisplay dpy) dp->refs--; dp->numTotalConfigs = 0; delete [] dp->configs; - clearTLS(); + return res; } @@ -1150,6 +1150,27 @@ EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) return result; } +static void loseCurrent(egl_context_t * cur_c) +{ + if (cur_c) { + egl_surface_t * cur_r = get_surface(cur_c->read); + egl_surface_t * cur_d = get_surface(cur_c->draw); + + // by construction, these are either 0 or valid (possibly terminated) + // it should be impossible for these to be invalid + ContextRef _cur_c(cur_c); + SurfaceRef _cur_r(cur_r); + SurfaceRef _cur_d(cur_d); + + cur_c->read = NULL; + cur_c->draw = NULL; + + _cur_c.release(); + _cur_r.release(); + _cur_d.release(); + } +} + EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { @@ -1178,13 +1199,9 @@ EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, // these are the current objects structs egl_context_t * cur_c = get_context(getContext()); - egl_surface_t * cur_r = NULL; - egl_surface_t * cur_d = NULL; if (ctx != EGL_NO_CONTEXT) { c = get_context(ctx); - cur_r = get_surface(c->read); - cur_d = get_surface(c->draw); impl_ctx = c->context; } else { // no context given, use the implementation of the current context @@ -1230,30 +1247,21 @@ EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, } if (result == EGL_TRUE) { - // by construction, these are either 0 or valid (possibly terminated) - // it should be impossible for these to be invalid - ContextRef _cur_c(cur_c); - SurfaceRef _cur_r(cur_r); - SurfaceRef _cur_d(cur_d); - // cur_c has to be valid here (but could be terminated) + loseCurrent(cur_c); + if (ctx != EGL_NO_CONTEXT) { setGlThreadSpecific(c->cnx->hooks[c->version]); setContext(ctx); _c.acquire(); + _r.acquire(); + _d.acquire(); + c->read = read; + c->draw = draw; } else { setGlThreadSpecific(&gHooksNoContext); setContext(EGL_NO_CONTEXT); } - _cur_c.release(); - - _r.acquire(); - _cur_r.release(); - if (c) c->read = read; - - _d.acquire(); - _cur_d.release(); - if (c) c->draw = draw; } return result; } @@ -1637,6 +1645,9 @@ EGLenum eglQueryAPI(void) EGLBoolean eglReleaseThread(void) { + // If there is context bound to the thread, release it + loseCurrent(get_context(getContext())); + for (int i=0 ; i<IMPL_NUM_IMPLEMENTATIONS ; i++) { egl_connection_t* const cnx = &gEGLImpl[i]; if (cnx->dso) { diff --git a/packages/DefaultContainerService/res/values-ro/strings.xml b/packages/DefaultContainerService/res/values-ro/strings.xml index 69de00f..cf9d709 100644 --- a/packages/DefaultContainerService/res/values-ro/strings.xml +++ b/packages/DefaultContainerService/res/values-ro/strings.xml @@ -20,5 +20,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="service_name" msgid="4841491635055379553">"Ajutor accesare pachet"</string> + <!-- outdated translation 2860331691588000093 --> <string name="service_name" msgid="4841491635055379553">"Ajutor accesare pachet"</string> </resources> diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_4g.png Binary files differnew file mode 100644 index 0000000..1aea612 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_connected_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_4g.png Binary files differnew file mode 100644 index 0000000..425535e --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_connected_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_4g.png Binary files differnew file mode 100644 index 0000000..fcad363 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_4g.png Binary files differnew file mode 100644 index 0000000..4ff7db3 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_inandout_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_4g.png Binary files differnew file mode 100644 index 0000000..2c4a07f --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_out_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_4g.png Binary files differnew file mode 100644 index 0000000..879c703 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_in_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inadnout_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inadnout_e.png Binary files differnew file mode 100644 index 0000000..61a7503 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inadnout_e.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_4g.png Binary files differnew file mode 100644 index 0000000..c5edf2c --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_inandout_4g.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_4g.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_4g.png Binary files differnew file mode 100644 index 0000000..ddf88be --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_out_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_1x.png Binary files differindex 78ece9e..c77e61e 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_3g.png Binary files differindex 31fc1b0..b9f721a 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_4g.png Binary files differnew file mode 100644 index 0000000..cff969e --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_e.png Binary files differindex 19adb4b..d2d7ab3 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_g.png Binary files differindex fd419ea..83ce6d0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_h.png Binary files differindex 94e77ae..abe511f 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_connected_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_1x.png Binary files differindex 91328c0..d685af8 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_3g.png Binary files differindex 2fee692..8c697a1 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_4g.png Binary files differnew file mode 100644 index 0000000..9a4b807 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_e.png Binary files differindex d0968aa..eb11d04 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_g.png Binary files differindex 991228b..6e54de0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_h.png Binary files differindex ae03e38..5bfb33b 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_connected_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_1x.png Binary files differindex 97b011e..119067b 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_3g.png Binary files differindex a826866..a70cc2e 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_4g.png Binary files differnew file mode 100644 index 0000000..ea3dba7 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_e.png Binary files differindex f6a6891..53221b9 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_g.png Binary files differindex 19b9816..11d44d0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_h.png Binary files differindex f8c0961..9defd79 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_in_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_1x.png Binary files differindex 22deb70..136576d 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_3g.png Binary files differindex c7c1b49..26ca31f 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_4g.png Binary files differnew file mode 100644 index 0000000..de8c5ee --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_e.png Binary files differindex d9a0702..64dbf3c 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_g.png Binary files differindex 6beed8a..34923fb 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_h.png Binary files differindex e4179c1..506b5c6 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_inandout_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_1x.png Binary files differindex 4b2f86d..163976f 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_3g.png Binary files differindex 6779604..a6af649 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_4g.png Binary files differnew file mode 100644 index 0000000..0c08e52 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_e.png Binary files differindex 1309a97..1d02edb 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_g.png Binary files differindex 2fc1e8e..edc9536 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_h.png Binary files differindex 0eef2c1..8376817 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_fully_out_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_1x.png Binary files differindex f8904e2..ecef547 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_3g.png Binary files differindex 3ef306e..a7c48b6 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_4g.png Binary files differnew file mode 100644 index 0000000..f4bcd9a --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_e.png Binary files differindex 2ff6d90..b46bb3a 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_g.png Binary files differindex 8ff49b0..e8b70f2 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_h.png Binary files differindex f416203..4e23c4e 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_in_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inadnout_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inadnout_e.png Binary files differnew file mode 100644 index 0000000..ced9175 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inadnout_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_1x.png Binary files differindex 24b7daa..92d4a19 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_3g.png Binary files differindex 5ea9142..a208736 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_4g.png Binary files differnew file mode 100644 index 0000000..f407bc9 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_g.png Binary files differindex 002bf46..b8a65c2 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_h.png Binary files differindex 924b84f..a978b68 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_inandout_h.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_1x.png Binary files differindex bd0d1ca..710dd52 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_1x.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_3g.png Binary files differindex f583eec..a7b35e4 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_3g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_4g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_4g.png Binary files differnew file mode 100644 index 0000000..bb05449 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_4g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_e.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_e.png Binary files differindex 66940ea..a144222 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_e.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_e.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_g.png Binary files differindex 0381f52..b0eafb6 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_g.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_h.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_h.png Binary files differindex 0b84fe8..f6b83d0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_h.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_out_h.png diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java index 5c52783..1368baa 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java @@ -30,7 +30,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -83,7 +83,7 @@ public class UsbStorageActivity extends Activity private BroadcastReceiver mUsbStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Usb.ACTION_USB_STATE)) { + if (intent.getAction().equals(UsbManager.ACTION_USB_STATE)) { handleUsbStateChanged(intent); } } @@ -175,7 +175,7 @@ public class UsbStorageActivity extends Activity super.onResume(); mStorageManager.registerListener(mStorageListener); - registerReceiver(mUsbStateReceiver, new IntentFilter(Usb.ACTION_USB_STATE)); + registerReceiver(mUsbStateReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE)); try { mAsyncStorageHandler.post(new Runnable() { @Override @@ -199,7 +199,7 @@ public class UsbStorageActivity extends Activity } private void handleUsbStateChanged(Intent intent) { - boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); + boolean connected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); if (!connected) { // It was disconnected from the plug, so finish finish(); diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 8560d16..40cfda9 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -1068,59 +1068,32 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { if (checkType == prevNetType) continue; if (mNetAttributes[checkType] == null) continue; + if (mNetAttributes[checkType].isDefault() == false) continue; if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE && noMobileData) { Slog.e(TAG, "not failing over to mobile type " + checkType + " because Mobile Data Disabled"); continue; } - if (mNetAttributes[checkType].isDefault()) { - /* TODO - if we have multiple nets we could use - * we may want to put more thought into which we choose - */ - if (checkType == mNetworkPreference) { - newType = checkType; - break; - } - if (mNetAttributes[checkType].mPriority > newPriority) { - newType = checkType; - newPriority = mNetAttributes[newType].mPriority; - } + NetworkStateTracker tracker = mNetTrackers[checkType]; + NetworkInfo info = tracker.getNetworkInfo(); + if (!info.isConnectedOrConnecting() || + tracker.isTeardownRequested()) { + info.setFailover(true); + tracker.reconnect(); } - } + if (DBG) Slog.d(TAG, "Attempting to switch to " + info.getTypeName()); - if (newType != -1) { - newNet = mNetTrackers[newType]; - /** - * See if the other network is available to fail over to. - * If is not available, we enable it anyway, so that it - * will be able to connect when it does become available, - * but we report a total loss of connectivity rather than - * report that we are attempting to fail over. - */ - if (newNet.isAvailable()) { - NetworkInfo switchTo = newNet.getNetworkInfo(); - switchTo.setFailover(true); - if (!switchTo.isConnectedOrConnecting() || - newNet.isTeardownRequested()) { - newNet.reconnect(); - } - if (DBG) { - if (switchTo.isConnected()) { - Slog.v(TAG, "Switching to already connected " + - switchTo.getTypeName()); - } else { - Slog.v(TAG, "Attempting to switch to " + - switchTo.getTypeName()); - } - } - } else { - newNet.reconnect(); - newNet = null; // not officially avail.. try anyway, but - // report no failover + // figure out if this is the highest priority network + // so we send an appropriate return value + if (checkType == mNetworkPreference) { + newType = checkType; + } + if (mNetAttributes[checkType].mPriority > newPriority && + newType != mNetworkPreference) { + newType = checkType; + newPriority = mNetAttributes[checkType].mPriority; } - } else { - Slog.e(TAG, "Network failover failing."); } } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 4da5eb2..6640ab7 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -38,7 +38,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; @@ -352,14 +352,12 @@ public class NotificationManagerService extends INotificationManager.Stub mBatteryFull = batteryFull; updateLights(); } - } else if (action.equals(Usb.ACTION_USB_STATE)) { + } else if (action.equals(UsbManager.ACTION_USB_STATE)) { Bundle extras = intent.getExtras(); - boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED); - boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals( - extras.getString(Usb.USB_FUNCTION_ADB))); + boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED); + boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals( + extras.getString(UsbManager.USB_FUNCTION_ADB))); updateAdbNotification(usbConnected && adbEnabled); - } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) { - updateAdbNotification(false); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) @@ -475,7 +473,7 @@ public class NotificationManagerService extends INotificationManager.Stub // register for battery changed notifications IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index df69b76..25175e2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -122,7 +122,7 @@ class ServerThread extends Thread { BluetoothA2dpService bluetoothA2dp = null; HeadsetObserver headset = null; DockObserver dock = null; - UsbObserver usb = null; + UsbService usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; @@ -399,9 +399,9 @@ class ServerThread extends Thread { try { Slog.i(TAG, "USB Observer"); // Listen for USB changes - usb = new UsbObserver(context); + usb = new UsbService(context); } catch (Throwable e) { - Slog.e(TAG, "Failure starting UsbObserver", e); + Slog.e(TAG, "Failure starting UsbService", e); } try { @@ -493,7 +493,7 @@ class ServerThread extends Thread { final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; - final UsbObserver usbF = usb; + final UsbService usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java deleted file mode 100644 index d08fe9b..0000000 --- a/services/java/com/android/server/UsbObserver.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.server; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.hardware.Usb; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.os.UEventObserver; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.util.ArrayList; - -/** - * <p>UsbObserver monitors for changes to USB state. - */ -class UsbObserver extends UEventObserver { - private static final String TAG = UsbObserver.class.getSimpleName(); - private static final boolean LOG = false; - - private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration"; - private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/"; - private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state"; - private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite"; - - private static final int MSG_UPDATE = 0; - - private int mUsbConfig = 0; - private int mPreviousUsbConfig = 0; - - // lists of enabled and disabled USB functions - private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); - private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); - - private boolean mSystemReady; - - private final Context mContext; - - private PowerManagerService mPowerManager; - - public UsbObserver(Context context) { - mContext = context; - init(); // set initial status - - startObserving(USB_CONFIGURATION_MATCH); - startObserving(USB_FUNCTIONS_MATCH); - } - - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } - - synchronized (this) { - String switchState = event.get("SWITCH_STATE"); - if (switchState != null) { - try { - int newConfig = Integer.parseInt(switchState); - if (newConfig != mUsbConfig) { - mPreviousUsbConfig = mUsbConfig; - mUsbConfig = newConfig; - // trigger an Intent broadcast - if (mSystemReady) { - update(); - } - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } else { - String function = event.get("FUNCTION"); - String enabledStr = event.get("ENABLED"); - if (function != null && enabledStr != null) { - // Note: we do not broadcast a change when a function is enabled or disabled. - // We just record the state change for the next broadcast. - boolean enabled = "1".equals(enabledStr); - if (enabled) { - if (!mEnabledFunctions.contains(function)) { - mEnabledFunctions.add(function); - } - mDisabledFunctions.remove(function); - } else { - if (!mDisabledFunctions.contains(function)) { - mDisabledFunctions.add(function); - } - mEnabledFunctions.remove(function); - } - } - } - } - } - private final void init() { - char[] buffer = new char[1024]; - - try { - FileReader file = new FileReader(USB_CONFIGURATION_PATH); - int len = file.read(buffer, 0, 1024); - mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim()); - - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB configuration switch support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - - try { - File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); - for (int i = 0; i < files.length; i++) { - File file = new File(files[i], "enable"); - FileReader reader = new FileReader(file); - int len = reader.read(buffer, 0, 1024); - int value = Integer.valueOf((new String(buffer, 0, len)).trim()); - String functionName = files[i].getName(); - if (value == 1) { - mEnabledFunctions.add(functionName); - } else { - mDisabledFunctions.add(functionName); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB composite class support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - } - - void systemReady() { - synchronized (this) { - update(); - mSystemReady = true; - } - } - - private final void update() { - mHandler.sendEmptyMessage(MSG_UPDATE); - } - - private final Handler mHandler = new Handler() { - private void addEnabledFunctions(Intent intent) { - // include state of all USB functions in our extras - for (int i = 0; i < mEnabledFunctions.size(); i++) { - intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED); - } - for (int i = 0; i < mDisabledFunctions.size(); i++) { - intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED); - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE: - synchronized (this) { - final ContentResolver cr = mContext.getContentResolver(); - - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); - return; - } - // Send an Intent containing connected/disconnected state - // and the enabled/disabled state of all USB functions - Intent intent; - boolean usbConnected = (mUsbConfig != 0); - if (usbConnected) { - intent = new Intent(Usb.ACTION_USB_CONNECTED); - addEnabledFunctions(intent); - } else { - intent = new Intent(Usb.ACTION_USB_DISCONNECTED); - } - mContext.sendBroadcast(intent); - - // send a sticky broadcast for clients interested in both connect and disconnect - intent = new Intent(Usb.ACTION_USB_STATE); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Usb.USB_CONNECTED, usbConnected); - addEnabledFunctions(intent); - mContext.sendStickyBroadcast(intent); - } - break; - } - } - }; -} diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java new file mode 100644 index 0000000..578db0e --- /dev/null +++ b/services/java/com/android/server/UsbService.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2010 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 com.android.server; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.UsbManager; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; + +/** + * <p>UsbService monitors for changes to USB state. + */ +class UsbService { + private static final String TAG = UsbService.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONNECTED_MATCH = + "DEVPATH=/devices/virtual/switch/usb_connected"; + private static final String USB_CONFIGURATION_MATCH = + "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = + "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONNECTED_PATH = + "/sys/class/switch/usb_connected/state"; + private static final String USB_CONFIGURATION_PATH = + "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = + "/sys/class/usb_composite"; + + private static final int MSG_UPDATE = 0; + + // Delay for debouncing USB disconnects. + // We often get rapid connect/disconnect events when enabling USB functions, + // which need debouncing. + private static final int UPDATE_DELAY = 1000; + + // current connected and configuration state + private int mConnected; + private int mConfiguration; + + // last broadcasted connected and configuration state + private int mLastConnected = -1; + private int mLastConfiguration = -1; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private final Context mContext; + + private final UEventObserver mUEventObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (this) { + String name = event.get("SWITCH_NAME"); + String state = event.get("SWITCH_STATE"); + if (name != null && state != null) { + try { + int intState = Integer.parseInt(state); + if ("usb_connected".equals(name)) { + mConnected = intState; + // trigger an Intent broadcast + if (mSystemReady) { + // debounce disconnects + update(mConnected == 0); + } + } else if ("usb_configuration".equals(name)) { + mConfiguration = intState; + // trigger an Intent broadcast + if (mSystemReady) { + update(mConnected == 0); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + boolean enabled = "1".equals(enabledStr); + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + } + } + } + }; + + public UsbService(Context context) { + mContext = context; + init(); // set initial status + + if (mConfiguration >= 0) { + mUEventObserver.startObserving(USB_CONNECTED_MATCH); + mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); + mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); + } + } + + private final void init() { + char[] buffer = new char[1024]; + + mConfiguration = -1; + try { + FileReader file = new FileReader(USB_CONNECTED_PATH); + int len = file.read(buffer, 0, 1024); + file.close(); + mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(USB_CONFIGURATION_PATH); + len = file.read(buffer, 0, 1024); + file.close(); + mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); + } catch (FileNotFoundException e) { + Slog.i(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + if (mConfiguration < 0) + return; + + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + reader.close(); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + void systemReady() { + synchronized (this) { + update(false); + mSystemReady = true; + } + } + + private final void update(boolean delayed) { + mHandler.removeMessages(MSG_UPDATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0); + } + + private final Handler mHandler = new Handler() { + private void addEnabledFunctions(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE: + synchronized (this) { + if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + + mLastConnected = mConnected; + mLastConfiguration = mConfiguration; + + // send a sticky broadcast containing current USB state + Intent intent = new Intent(UsbManager.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); + intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); + addEnabledFunctions(intent); + mContext.sendStickyBroadcast(intent); + } + } + break; + } + } + }; +} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index a73a4ce..7652a26 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.hardware.Usb; +import android.hardware.UsbManager; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; @@ -111,14 +111,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection - // mUsbHandler message - static final int USB_STATE_CHANGE = 1; - static final int USB_DISCONNECTED = 0; - static final int USB_CONNECTED = 1; - - // Time to delay before processing USB disconnect events - static final long USB_DISCONNECT_DELAY = 1000; - public Tethering(Context context, Looper looper) { mContext = context; mLooper = looper; @@ -143,7 +135,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Usb.ACTION_USB_STATE); + filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -429,25 +421,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - private Handler mUsbHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - mUsbConnected = (msg.arg1 == USB_CONNECTED); - updateUsbStatus(); - } - }; - private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Usb.ACTION_USB_STATE)) { - // process connect events immediately, but delay handling disconnects - // to debounce USB configuration changes - boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); - Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE, - (connected ? USB_CONNECTED : USB_DISCONNECTED), 0); - mUsbHandler.removeMessages(USB_STATE_CHANGE); - mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY); + if (action.equals(UsbManager.ACTION_USB_STATE)) { + mUsbConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); + updateUsbStatus(); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); diff --git a/services/jni/Android.mk b/services/jni/Android.mk index c90879d..de8f158 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \ com_android_server_LightsService.cpp \ com_android_server_PowerManagerService.cpp \ com_android_server_SystemServer.cpp \ + com_android_server_UsbService.cpp \ com_android_server_VibratorService.cpp \ com_android_server_location_GpsLocationProvider.cpp \ onload.cpp diff --git a/services/jni/com_android_server_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp new file mode 100644 index 0000000..dff8665 --- /dev/null +++ b/services/jni/com_android_server_UsbService.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 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. + */ + +#define LOG_TAG "UsbService" +#include "utils/Log.h" + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include "utils/Vector.h" + +#include <stdio.h> + +namespace android +{ + +int register_android_server_UsbService(JNIEnv *env) +{ + + return 0; +} + +}; diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index cd4f0a4..613683b 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -9,6 +9,7 @@ int register_android_server_BatteryService(JNIEnv* env); int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_PowerManagerService(JNIEnv* env); +int register_android_server_UsbService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_location_GpsLocationProvider(JNIEnv* env); @@ -32,6 +33,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); register_android_server_BatteryService(env); + register_android_server_UsbService(env); register_android_server_VibratorService(env); register_android_server_SystemServer(env); register_android_server_location_GpsLocationProvider(env); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4876946..4b3235d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2427,7 +2427,7 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const } break; } - if (++name > 31) + if (++name >= SharedBufferStack::NUM_LAYERS_MAX) name = NO_MEMORY; } while(name >= 0); diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index 0865d6f..3890a98 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -737,7 +737,14 @@ public class CDMAPhone extends PhoneBase { String number = null; SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); // TODO: The default value of voicemail number should be read from a system property - number = sp.getString(VM_NUMBER_CDMA, "*86"); + + // Read platform settings for dynamic voicemail number + if (getContext().getResources().getBoolean(com.android.internal + .R.bool.config_telephony_use_own_number_for_voicemail)) { + number = sp.getString(VM_NUMBER_CDMA, getLine1Number()); + } else { + number = sp.getString(VM_NUMBER_CDMA, "*86"); + } return number; } diff --git a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java index b642541..ec3d20a 100755 --- a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java +++ b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java @@ -307,8 +307,15 @@ public class UsimPhoneBookManager extends Handler implements IccConstants { fileIds = mPbrFile.mFileIds.get(recNum); if (fileIds == null || fileIds.isEmpty()) return; + + int extEf = 0; + // Only call fileIds.get while EFEXT1_TAG is available + if (fileIds.containsKey(USIM_EFEXT1_TAG)) { + extEf = fileIds.get(USIM_EFEXT1_TAG); + } + mAdnCache.requestLoadAllAdnLike(fileIds.get(USIM_EFADN_TAG), - fileIds.get(USIM_EFEXT1_TAG), obtainMessage(EVENT_USIM_ADN_LOAD_DONE)); + extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE)); try { mLock.wait(); } catch (InterruptedException e) { diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore index 0ec5000..c5e82d7 100644 --- a/tools/layoutlib/.gitignore +++ b/tools/layoutlib/.gitignore @@ -1,2 +1 @@ -bridge/bin -create/bin +bin
\ No newline at end of file diff --git a/tools/layoutlib/README b/tools/layoutlib/README new file mode 100644 index 0000000..0fea9bd --- /dev/null +++ b/tools/layoutlib/README @@ -0,0 +1,4 @@ +Layoutlib is a custom version of the android View framework designed to run inside Eclipse. +The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices. + +None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath index 70140d8..9fb000e 100644 --- a/tools/layoutlib/bridge/.classpath +++ b/tools/layoutlib/bridge/.classpath @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/> - <classpathentry kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> - <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/layoutlib_api/layoutlib_api-prebuilt.jar"/> - <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/> - <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/layoutlib.jar" sourcepath="/ANDROID_SRC/frameworks/base/core/java"/> - <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/ninepatch.jar" sourcepath="/ANDROID_SRC/development/tools/ninepatch/src"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/layoutlib_api/layoutlib_api-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/> + <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/prebuilt/common/ninepatch/ninepatch-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/tools-common/tools-common-prebuilt.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index b7a602a..687a91f 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -17,15 +17,22 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_JAVA_RESOURCE_DIRS := resources + LOCAL_JAVA_LIBRARIES := \ kxml2-2.3.0 \ layoutlib_api-prebuilt \ - ninepatch + tools-common-prebuilt -LOCAL_STATIC_JAVA_LIBRARIES := temp_layoutlib +LOCAL_STATIC_JAVA_LIBRARIES := \ + temp_layoutlib \ + ninepatch-prebuilt LOCAL_MODULE := layoutlib include $(BUILD_HOST_JAVA_LIBRARY) +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..bd44b52 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..a4be298 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..c629387 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png Binary files differnew file mode 100644 index 0000000..eb7c1a4 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png diff --git a/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml b/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml new file mode 100644 index 0000000..d3c492e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/phone_system_bar.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="3dip" + android:layout_marginRight="5dip"/> +</merge> diff --git a/tools/layoutlib/bridge/resources/bars/title_bar.xml b/tools/layoutlib/bridge/resources/bars/title_bar.xml new file mode 100644 index 0000000..76d78d9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/title_bar.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</merge> diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java new file mode 100644 index 0000000..413894b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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.content.res; + +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.Resources.NotFoundException; +import android.content.res.Resources.Theme; +import android.util.AttributeSet; +import android.util.TypedValue; + +/** + * Delegate used to provide new implementation of a select few methods of {@link Theme} + * + * Through the layoutlib_create tool, the original methods of Theme have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class Resources_Theme_Delegate { + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int[] attrs) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + int resid, int[] attrs) + throws NotFoundException { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + } + + @LayoutlibDelegate + /*package*/ static TypedArray obtainStyledAttributes( + Resources thisResources, Theme thisTheme, + AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + return RenderSessionImpl.getCurrentContext().obtainStyledAttributes( + set, attrs, defStyleAttr, defStyleRes); + } + + @LayoutlibDelegate + /*package*/ static boolean resolveAttribute( + Resources thisResources, Theme thisTheme, + int resid, TypedValue outValue, + boolean resolveRefs) { + return RenderSessionImpl.getCurrentContext().resolveThemeAttribute( + resid, outValue, resolveRefs); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java new file mode 100644 index 0000000..a50a2bd --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.AvoidXfermode + * + * Through the layoutlib_create tool, the original native methods of AvoidXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original AvoidXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class AvoidXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Avoid Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor, int tolerance, int nativeMode) { + AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java deleted file mode 100644 index 35f022e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2008 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.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import javax.imageio.ImageIO; - -public final class Bitmap extends _Original_Bitmap { - - private BufferedImage mImage; - - public Bitmap(File input) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(input); - } - - public Bitmap(InputStream is) throws IOException { - super(1, true, null, -1); - - mImage = ImageIO.read(is); - } - - Bitmap(BufferedImage image) { - super(1, true, null, -1); - mImage = image; - } - - public BufferedImage getImage() { - return mImage; - } - - // ----- overriden methods - - public enum Config { - // these native values must match up with the enum in SkBitmap.h - ALPHA_8 (2), - RGB_565 (4), - ARGB_4444 (5), - ARGB_8888 (6); - - Config(int ni) { - this.nativeInt = ni; - } - final int nativeInt; - - /* package */ static Config nativeToConfig(int ni) { - return sConfigs[ni]; - } - - private static Config sConfigs[] = { - null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 - }; - } - - @Override - public int getWidth() { - return mImage.getWidth(); - } - - @Override - public int getHeight() { - return mImage.getHeight(); - } - - /** - * Returns an immutable bitmap from the source bitmap. The new bitmap may - * be the same object as source, or a copy may have been made. - */ - public static Bitmap createBitmap(Bitmap src) { - return createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), null, false); - } - - /** - * Returns an immutable bitmap from the specified subset of the source - * bitmap. The new bitmap may be the same object as source, or a copy may - * have been made. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, - int width, int height) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - /** - * Returns an immutable bitmap from subset of the source bitmap, - * transformed by the optional matrix. - * - * @param source The bitmap we are subsetting - * @param x The x coordinate of the first pixel in source - * @param y The y coordinate of the first pixel in source - * @param width The number of pixels in each row - * @param height The number of rows - * @param m Option matrix to be applied to the pixels - * @param filter true if the source should be filtered. - * Only applies if the matrix contains more than just - * translation. - * @return A bitmap that represents the specified subset of source - * @throws IllegalArgumentException if the x, y, width, height values are - * outside of the dimensions of the source bitmap. - */ - public static Bitmap createBitmap(Bitmap source, int x, int y, int width, - int height, Matrix m, boolean filter) { - checkXYSign(x, y); - checkWidthHeight(width, height); - if (x + width > source.getWidth()) { - throw new IllegalArgumentException( - "x + width must be <= bitmap.width()"); - } - if (y + height > source.getHeight()) { - throw new IllegalArgumentException( - "y + height must be <= bitmap.height()"); - } - - // check if we can just return our argument unchanged - if (!source.isMutable() && x == 0 && y == 0 - && width == source.getWidth() && height == source.getHeight() - && (m == null || m.isIdentity())) { - return source; - } - - if (m == null || m.isIdentity()) { - return new Bitmap(source.mImage.getSubimage(x, y, width, height)); - } - - int neww = width; - int newh = height; - Paint paint; - - Rect srcR = new Rect(x, y, x + width, y + height); - RectF dstR = new RectF(0, 0, width, height); - - /* the dst should have alpha if the src does, or if our matrix - doesn't preserve rectness - */ - boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect(); - RectF deviceR = new RectF(); - m.mapRect(deviceR, dstR); - neww = Math.round(deviceR.width()); - newh = Math.round(deviceR.height()); - - Canvas canvas = new Canvas(neww, newh); - - canvas.translate(-deviceR.left, -deviceR.top); - canvas.concat(m); - paint = new Paint(); - paint.setFilterBitmap(filter); - if (!m.rectStaysRect()) { - paint.setAntiAlias(true); - } - - canvas.drawBitmap(source, srcR, dstR, paint); - - return new Bitmap(canvas.getImage()); - } - - /** - * Returns a mutable bitmap with the specified width and height. - * - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. - * @throws IllegalArgumentException if the width or height are <= 0 - */ - public static Bitmap createBitmap(int width, int height, Config config) { - return new Bitmap(new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * @param offset Number of values to skip before the first color in the - * array of colors. - * @param stride Number of colors in the array between rows (must be >= - * width or <= -width). - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int offset, int stride, - int width, int height, Config config) { - checkWidthHeight(width, height); - if (Math.abs(stride) < width) { - throw new IllegalArgumentException("abs(stride) must be >= width"); - } - int lastScanline = offset + (height - 1) * stride; - int length = colors.length; - if (offset < 0 || (offset + width > length) - || lastScanline < 0 - || (lastScanline + width > length)) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO: create an immutable bitmap... - throw new UnsupportedOperationException(); - } - - /** - * Returns a immutable bitmap with the specified width and height, with each - * pixel value set to the corresponding value in the colors array. - * - * @param colors Array of {@link Color} used to initialize the pixels. - * This array must be at least as large as width * height. - * @param width The width of the bitmap - * @param height The height of the bitmap - * @param config The bitmap config to create. If the config does not - * support per-pixel alpha (e.g. RGB_565), then the alpha - * bytes in the colors[] will be ignored (assumed to be FF) - * @throws IllegalArgumentException if the width or height are <= 0, or if - * the color array's length is less than the number of pixels. - */ - public static Bitmap createBitmap(int colors[], int width, int height, - Config config) { - return createBitmap(colors, 0, width, width, height, config); - } - - public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, - int dstHeight, boolean filter) { - Matrix m; - synchronized (Bitmap.class) { - // small pool of just 1 matrix - m = sScaleMatrix; - sScaleMatrix = null; - } - - if (m == null) { - m = new Matrix(); - } - - final int width = src.getWidth(); - final int height = src.getHeight(); - final float sx = dstWidth / (float)width; - final float sy = dstHeight / (float)height; - m.setScale(sx, sy); - Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter); - - synchronized (Bitmap.class) { - // do we need to check for null? why not just assign everytime? - if (sScaleMatrix == null) { - sScaleMatrix = m; - } - } - - return b; - } - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java deleted file mode 100644 index e978fe8..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright (C) 2010 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 android.content.res.AssetManager; -import android.content.res.Resources; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import java.io.BufferedInputStream; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Creates Bitmap objects from various sources, including files, streams, - * and byte-arrays. - */ -public class BitmapFactory { - public static class Options { - /** - * Create a default Options object, which if left unchanged will give - * the same result from the decoder as if null were passed. - */ - public Options() { - inDither = true; - inScaled = true; - } - - /** - * If set to true, the decoder will return null (no bitmap), but - * the out... fields will still be set, allowing the caller to query - * the bitmap without having to allocate the memory for its pixels. - */ - public boolean inJustDecodeBounds; - - /** - * If set to a value > 1, requests the decoder to subsample the original - * image, returning a smaller image to save memory. The sample size is - * the number of pixels in either dimension that correspond to a single - * pixel in the decoded bitmap. For example, inSampleSize == 4 returns - * an image that is 1/4 the width/height of the original, and 1/16 the - * number of pixels. Any value <= 1 is treated the same as 1. Note: the - * decoder will try to fulfill this request, but the resulting bitmap - * may have different dimensions that precisely what has been requested. - * Also, powers of 2 are often faster/easier for the decoder to honor. - */ - public int inSampleSize; - - /** - * If this is non-null, the decoder will try to decode into this - * internal configuration. If it is null, or the request cannot be met, - * the decoder will try to pick the best matching config based on the - * system's screen depth, and characteristics of the original image such - * as if it has per-pixel alpha (requiring a config that also does). - */ - public Bitmap.Config inPreferredConfig; - - /** - * If dither is true, the decoder will attempt to dither the decoded - * image. - */ - public boolean inDither; - - /** - * The pixel density to use for the bitmap. This will always result - * in the returned bitmap having a density set for it (see - * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition, - * if {@link #inScaled} is set (which it is by default} and this - * density does not match {@link #inTargetDensity}, then the bitmap - * will be scaled to the target density before being returned. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated with the resource. The other - * functions will leave it as-is and no density will be applied. - * - * @see #inTargetDensity - * @see #inScreenDensity - * @see #inScaled - * @see Bitmap#setDensity(int) - * @see android.util.DisplayMetrics#densityDpi - */ - public int inDensity; - - /** - * The pixel density of the destination this bitmap will be drawn to. - * This is used in conjunction with {@link #inDensity} and - * {@link #inScaled} to determine if and how to scale the bitmap before - * returning it. - * - * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, - * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, - * and {@link BitmapFactory#decodeResourceStream} - * will fill in the density associated the Resources object's - * DisplayMetrics. The other - * functions will leave it as-is and no scaling for density will be - * performed. - * - * @see #inDensity - * @see #inScreenDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inTargetDensity; - - /** - * The pixel density of the actual screen that is being used. This is - * purely for applications running in density compatibility code, where - * {@link #inTargetDensity} is actually the density the application - * sees rather than the real screen density. - * - * <p>By setting this, you - * allow the loading code to avoid scaling a bitmap that is currently - * in the screen density up/down to the compatibility density. Instead, - * if {@link #inDensity} is the same as {@link #inScreenDensity}, the - * bitmap will be left as-is. Anything using the resulting bitmap - * must also used {@link Bitmap#getScaledWidth(int) - * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight - * Bitmap.getScaledHeight} to account for any different between the - * bitmap's density and the target's density. - * - * <p>This is never set automatically for the caller by - * {@link BitmapFactory} itself. It must be explicitly set, since the - * caller must deal with the resulting bitmap in a density-aware way. - * - * @see #inDensity - * @see #inTargetDensity - * @see #inScaled - * @see android.util.DisplayMetrics#densityDpi - */ - public int inScreenDensity; - - /** - * When this flag is set, if {@link #inDensity} and - * {@link #inTargetDensity} are not 0, the - * bitmap will be scaled to match {@link #inTargetDensity} when loaded, - * rather than relying on the graphics system scaling it each time it - * is drawn to a Canvas. - * - * <p>This flag is turned on by default and should be turned off if you need - * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this - * flag and are always scaled. - */ - public boolean inScaled; - - /** - * If this is set to true, then the resulting bitmap will allocate its - * pixels such that they can be purged if the system needs to reclaim - * memory. In that instance, when the pixels need to be accessed again - * (e.g. the bitmap is drawn, getPixels() is called), they will be - * automatically re-decoded. - * - * For the re-decode to happen, the bitmap must have access to the - * encoded data, either by sharing a reference to the input - * or by making a copy of it. This distinction is controlled by - * inInputShareable. If this is true, then the bitmap may keep a shallow - * reference to the input. If this is false, then the bitmap will - * explicitly make a copy of the input data, and keep that. Even if - * sharing is allowed, the implementation may still decide to make a - * deep copy of the input data. - */ - public boolean inPurgeable; - - /** - * This field works in conjuction with inPurgeable. If inPurgeable is - * false, then this field is ignored. If inPurgeable is true, then this - * field determines whether the bitmap can share a reference to the - * input data (inputstream, array, etc.) or if it must make a deep copy. - */ - public boolean inInputShareable; - - /** - * Normally bitmap allocations count against the dalvik heap, which - * means they help trigger GCs when a lot have been allocated. However, - * in rare cases, the caller may want to allocate the bitmap outside of - * that heap. To request that, set inNativeAlloc to true. In these - * rare instances, it is solely up to the caller to ensure that OOM is - * managed explicitly by calling bitmap.recycle() as soon as such a - * bitmap is no longer needed. - * - * @hide pending API council approval - */ - public boolean inNativeAlloc; - - /** - * The resulting width of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outWidth will be set to -1. - */ - public int outWidth; - - /** - * The resulting height of the bitmap, set independent of the state of - * inJustDecodeBounds. However, if there is an error trying to decode, - * outHeight will be set to -1. - */ - public int outHeight; - - /** - * If known, this string is set to the mimetype of the decoded image. - * If not know, or there is an error, it is set to null. - */ - public String outMimeType; - - /** - * Temp storage to use for decoding. Suggest 16K or so. - */ - public byte[] inTempStorage; - - private native void requestCancel(); - - /** - * Flag to indicate that cancel has been called on this object. This - * is useful if there's an intermediary that wants to first decode the - * bounds and then decode the image. In that case the intermediary - * can check, inbetween the bounds decode and the image decode, to see - * if the operation is canceled. - */ - public boolean mCancel; - - /** - * This can be called from another thread while this options object is - * inside a decode... call. Calling this will notify the decoder that - * it should cancel its operation. This is not guaranteed to cancel - * the decode, but if it does, the decoder... operation will return - * null, or if inJustDecodeBounds is true, will set outWidth/outHeight - * to -1 - */ - public void requestCancelDecode() { - mCancel = true; - requestCancel(); - } - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeFile(String pathName, Options opts) { - Bitmap bm = null; - InputStream stream = null; - try { - stream = new FileInputStream(pathName); - bm = decodeStream(stream, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - */ - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - // do nothing here - } - } - } - return bm; - } - - /** - * Decode a file path into a bitmap. If the specified file name is null, - * or cannot be decoded into a bitmap, the function returns null. - * - * @param pathName complete path name for the file to be decoded. - * @return the resulting decoded bitmap, or null if it could not be decoded. - */ - public static Bitmap decodeFile(String pathName) { - return decodeFile(pathName, null); - } - - /** - * Decode a new Bitmap from an InputStream. This InputStream was obtained from - * resources, which we pass to be able to scale the bitmap accordingly. - */ - public static Bitmap decodeResourceStream(Resources res, TypedValue value, - InputStream is, Rect pad, Options opts) { - - if (opts == null) { - opts = new Options(); - } - - if (opts.inDensity == 0 && value != null) { - final int density = value.density; - if (density == TypedValue.DENSITY_DEFAULT) { - opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; - } else if (density != TypedValue.DENSITY_NONE) { - opts.inDensity = density; - } - } - - if (opts.inTargetDensity == 0 && res != null) { - opts.inTargetDensity = res.getDisplayMetrics().densityDpi; - } - - return decodeStream(is, pad, opts); - } - - /** - * Synonym for opening the given resource and calling - * {@link #decodeResourceStream}. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeResource(Resources res, int id, Options opts) { - Bitmap bm = null; - InputStream is = null; - - try { - final TypedValue value = new TypedValue(); - is = res.openRawResource(id, value); - - bm = decodeResourceStream(res, value, is, null, opts); - } catch (Exception e) { - /* do nothing. - If the exception happened on open, bm will be null. - If it happened on close, bm is still valid. - */ - } finally { - try { - if (is != null) is.close(); - } catch (IOException e) { - // Ignore - } - } - - return bm; - } - - /** - * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)} - * will null Options. - * - * @param res The resources object containing the image data - * @param id The resource id of the image data - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeResource(Resources res, int id) { - return decodeResource(res, id, null); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) { - if ((offset | length) < 0 || data.length < offset + length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge. - return null; - //return nativeDecodeByteArray(data, offset, length, opts); - } - - /** - * Decode an immutable bitmap from the specified byte array. - * - * @param data byte array of compressed image data - * @param offset offset into imageData for where the decoder should begin - * parsing. - * @param length the number of bytes, beginning at offset, to parse - * @return The decoded bitmap, or null if the image could not be decode. - */ - public static Bitmap decodeByteArray(byte[] data, int offset, int length) { - return decodeByteArray(data, offset, length, null); - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { - // we don't throw in this case, thus allowing the caller to only check - // the cache, and not force the image to be decoded. - if (is == null) { - return null; - } - - // we need mark/reset to work properly - - if (!is.markSupported()) { - is = new BufferedInputStream(is, 16 * 1024); - } - - // so we can call reset() if a given codec gives up after reading up to - // this many bytes. FIXME: need to find out from the codecs what this - // value should be. - is.mark(1024); - - Bitmap bm; - - if (is instanceof AssetManager.AssetInputStream) { - // FIXME: log this. - return null; - } else { - // pass some temp storage down to the native code. 1024 is made up, - // but should be large enough to avoid too many small calls back - // into is.read(...) This number is not related to the value passed - // to mark(...) above. - try { - bm = new Bitmap(is); - } catch (IOException e) { - return null; - } - } - - return finishDecode(bm, outPadding, opts); - } - - private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { - if (bm == null || opts == null) { - return bm; - } - - final int density = opts.inDensity; - if (density == 0) { - return bm; - } - - bm.setDensity(density); - final int targetDensity = opts.inTargetDensity; - if (targetDensity == 0 || density == targetDensity - || density == opts.inScreenDensity) { - return bm; - } - - byte[] np = bm.getNinePatchChunk(); - final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np); - if (opts.inScaled || isNinePatch) { - float scale = targetDensity / (float)density; - // TODO: This is very inefficient and should be done in native by Skia - final Bitmap oldBitmap = bm; - bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), - (int) (bm.getHeight() * scale + 0.5f), true); - oldBitmap.recycle(); - - if (isNinePatch) { - //np = nativeScaleNinePatch(np, scale, outPadding); - bm.setNinePatchChunk(np); - } - bm.setDensity(targetDensity); - } - - return bm; - } - - /** - * Decode an input stream into a bitmap. If the input stream is null, or - * cannot be used to decode a bitmap, the function returns null. - * The stream's position will be where ever it was after the encoded data - * was read. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is) { - return decodeStream(is, null, null); - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as-is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { - return null; - - /* FIXME: implement as needed - try { - if (MemoryFile.isMemoryFile(fd)) { - int mappedlength = MemoryFile.getMappedSize(fd); - MemoryFile file = new MemoryFile(fd, mappedlength, "r"); - InputStream is = file.getInputStream(); - Bitmap bm = decodeStream(is, outPadding, opts); - return finishDecode(bm, outPadding, opts); - } - } catch (IOException ex) { - // invalid filedescriptor, no need to call nativeDecodeFileDescriptor() - return null; - } - //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts); - //return finishDecode(bm, outPadding, opts); - */ - } - - /** - * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded - * return null. The position within the descriptor will not be changed when - * this returns, so the descriptor can be used again as is. - * - * @param fd The file descriptor containing the bitmap data to decode - * @return the decoded bitmap, or null - */ - public static Bitmap decodeFileDescriptor(FileDescriptor fd) { - return decodeFileDescriptor(fd, null, null); - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java new file mode 100644 index 0000000..9c86e80 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeResources.NinePatchInputStream; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.BitmapFactory.Options; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; + +/** + * Delegate implementing the native methods of android.graphics.BitmapFactory + * + * Through the layoutlib_create tool, the original native methods of BitmapFactory have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +/*package*/ class BitmapFactory_Delegate { + + // ------ Java delegates ------ + + @LayoutlibDelegate + /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { + if (bm == null || opts == null) { + return bm; + } + + final int density = opts.inDensity; + if (density == 0) { + return bm; + } + + bm.setDensity(density); + final int targetDensity = opts.inTargetDensity; + if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { + return bm; + } + + byte[] np = bm.getNinePatchChunk(); + final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); + // DELEGATE CHANGE: never scale 9-patch + if (opts.inScaled && isNinePatch == false) { + float scale = targetDensity / (float)density; + // TODO: This is very inefficient and should be done in native by Skia + final Bitmap oldBitmap = bm; + bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), + (int) (bm.getHeight() * scale + 0.5f), true); + oldBitmap.recycle(); + + if (isNinePatch) { + np = nativeScaleNinePatch(np, scale, outPadding); + bm.setNinePatchChunk(np); + } + bm.setDensity(targetDensity); + } + + return bm; + } + + + // ------ Native Delegates ------ + + @LayoutlibDelegate + /*package*/ static void nativeSetDefaultConfig(int nativeConfig) { + // pass + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, + Rect padding, Options opts) { + Bitmap bm = null; + + Density density = Density.MEDIUM; + if (opts != null) { + density = Density.getEnum(opts.inDensity); + } + + try { + if (is instanceof NinePatchInputStream) { + NinePatchInputStream npis = (NinePatchInputStream) is; + npis.disableFakeMarkSupport(); + + // load the bitmap as a nine patch + com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load( + npis, true /*is9Patch*/, false /*convert*/); + + // get the bitmap and chunk objects. + bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), true /*isMutable*/, + density); + NinePatchChunk chunk = ninePatch.getChunk(); + + // put the chunk in the bitmap + bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk)); + + // read the padding + int[] paddingarray = chunk.getPadding(); + padding.left = paddingarray[0]; + padding.top = paddingarray[1]; + padding.right = paddingarray[2]; + padding.bottom = paddingarray[3]; + } else { + // load the bitmap directly. + bm = Bitmap_Delegate.createBitmap(is, true, density); + } + } catch (IOException e) { + Bridge.getLog().error(null,"Failed to load image" , e, null); + } + + return bm; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, + Rect padding, Options opts) { + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts) { + return null; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset, + int length, Options opts) { + return null; + } + + @LayoutlibDelegate + /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) { + // don't scale for now. This should not be called anyway since we re-implement + // BitmapFactory.finishDecode(); + return chunk; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java deleted file mode 100644 index ad3974c..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 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.Paint; - -public class BitmapShader extends Shader { - - // we hold on just for the GC, since our native counterpart is using it - private final Bitmap mBitmap; - - /** - * Call this to create a new shader that will draw with a bitmap. - * - * @param bitmap The bitmap to use inside the shader - * @param tileX The tiling mode for x to draw the bitmap in. - * @param tileY The tiling mode for y to draw the bitmap in. - */ - public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) { - mBitmap = bitmap; - } - - //---------- Custom methods - - public Bitmap getBitmap() { - return mBitmap; - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java new file mode 100644 index 0000000..8eb0693 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.BitmapShader + * + * Through the layoutlib_create tool, the original native methods of BitmapShader have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original BitmapShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class BitmapShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_bitmap, int shaderTileModeX, + int shaderTileModeY) { + Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(native_bitmap); + if (bitmap == null) { + return 0; + } + + BitmapShader_Delegate newDelegate = new BitmapShader_Delegate( + bitmap.getImage(), + Shader_Delegate.getTileMode(shaderTileModeX), + Shader_Delegate.getTileMode(shaderTileModeY)); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private BitmapShader_Delegate(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY); + } + + private class BitmapShaderPaint implements java.awt.Paint { + private final java.awt.image.BufferedImage mImage; + private final TileMode mTileModeX; + private final TileMode mTileModeY; + + BitmapShaderPaint(java.awt.image.BufferedImage image, + TileMode tileModeX, TileMode tileModeY) { + mImage = image; + mTileModeX = tileModeX; + mTileModeY = tileModeY; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in BitmapShader", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel); + } + + private class BitmapShaderContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public BitmapShaderContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float fx, float fy) { + int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX); + int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY); + + return mImage.getRGB(x, y); + } + + private int getCoordinate(int i, int size, TileMode mode) { + if (i < 0) { + switch (mode) { + case CLAMP: + i = 0; + break; + case REPEAT: + i = size - 1 - (-i % size); + break; + case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + i = -i; + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } else if (i >= size) { + switch (mode) { + case CLAMP: + i = size - 1; + break; + case REPEAT: + i = i % size; + break; + case MIRROR: + int count = i / size; + i = i % size; + + if ((count % 2) == 1) { + i = size - 1 - i; + } + break; + } + } + + return i; + } + + + public int getTransparency() { + return java.awt.Paint.TRANSLUCENT; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java new file mode 100644 index 0000000..87c3eb6 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.resources.Density; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.os.Parcel; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.Buffer; +import java.util.Arrays; + +import javax.imageio.ImageIO; + +/** + * Delegate implementing the native methods of android.graphics.Bitmap + * + * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Bitmap class. + * + * @see DelegateManager + * + */ +public final class Bitmap_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Bitmap_Delegate> sManager = + new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private final Config mConfig; + private BufferedImage mImage; + private boolean mHasAlpha = true; + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Bitmap_Delegate} object. + */ + public static Bitmap_Delegate getDelegate(Bitmap bitmap) { + return sManager.getDelegate(bitmap.mNativeBitmap); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object. + */ + public static Bitmap_Delegate getDelegate(int native_bitmap) { + return sManager.getDelegate(native_bitmap); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given file content. + * + * @param input the file from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(File input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the file. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given stream content. + * + * @param input the stream from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density) + throws IOException { + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} + * + * @param image the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() + */ + public static Bitmap createBitmap(BufferedImage image, boolean isMutable, + Density density) throws IOException { + // create a delegate with the given image. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); + + return createBitmap(delegate, isMutable, density.getDpiValue()); + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public static BufferedImage getImage(Bitmap bitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap); + if (delegate == null) { + return null; + } + + return delegate.mImage; + } + + public static int getBufferedImageType(int nativeBitmapConfig) { + switch (Config.sConfigs[nativeBitmapConfig]) { + case ALPHA_8: + return BufferedImage.TYPE_INT_ARGB; + case RGB_565: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_4444: + return BufferedImage.TYPE_INT_ARGB; + case ARGB_8888: + return BufferedImage.TYPE_INT_ARGB; + } + + return BufferedImage.TYPE_INT_ARGB; + } + + /** + * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. + */ + public BufferedImage getImage() { + return mImage; + } + + /** + * Returns the Android bitmap config. Note that this not the config of the underlying + * Java2D bitmap. + */ + public Config getConfig() { + return mConfig; + } + + /** + * Returns the hasAlpha rendering hint + * @return true if the bitmap alpha should be used at render time + */ + public boolean hasAlpha() { + return mHasAlpha && mConfig != Config.RGB_565; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, + int height, int nativeConfig, boolean mutable) { + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + if (colors != null) { + image.setRGB(0, 0, width, height, colors, offset, stride); + } + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { + Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); + if (srcBmpDelegate == null) { + return null; + } + + BufferedImage srcImage = srcBmpDelegate.getImage(); + + int width = srcImage.getWidth(); + int height = srcImage.getHeight(); + + int imageType = getBufferedImageType(nativeConfig); + + // create the image + BufferedImage image = new BufferedImage(width, height, imageType); + + // copy the source image into the image. + int[] argb = new int[width * height]; + srcImage.getRGB(0, 0, width, height, argb, 0, width); + image.setRGB(0, 0, width, height, argb, 0, width); + + // create a delegate with the content of the stream. + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]); + + return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity()); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static void nativeRecycle(int nativeBitmap) { + sManager.removeJavaReferenceFor(nativeBitmap); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, + OutputStream stream, byte[] tempStorage) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.compress() is not supported", null /*data*/); + return true; + } + + @LayoutlibDelegate + /*package*/ static void nativeErase(int nativeBitmap, int color) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + BufferedImage image = delegate.mImage; + + Graphics2D g = image.createGraphics(); + try { + g.setColor(new java.awt.Color(color, true)); + + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeWidth(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeHeight(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getHeight(); + } + + @LayoutlibDelegate + /*package*/ static int nativeRowBytes(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int nativeConfig(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mConfig.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeHasAlpha(int nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return true; + } + + return delegate.mHasAlpha; + } + + @LayoutlibDelegate + /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return 0; + } + + return delegate.mImage.getRGB(x, y); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); + } + + + @LayoutlibDelegate + /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, color); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, + int stride, int x, int y, int width, int height) { + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) { + // FIXME implement native delegate + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { + // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", + null /*data*/); + return null; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable, + int density, Parcel p) { + // This is only called when sending a bitmap through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint, + int[] offsetXY) { + Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); + if (bitmap == null) { + return null; + } + + // get the paint which can be null if nativePaint is 0. + Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); + + if (paint != null && paint.getMaskFilter() != null) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + "MaskFilter not supported in Bitmap.extractAlpha", + null, null /*data*/); + } + + int alpha = paint != null ? paint.getAlpha() : 0xFF; + BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); + + // create the delegate. The actual Bitmap config is only an alpha channel + Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); + + // the density doesn't matter, it's set by the Java method. + return createBitmap(delegate, false /*isMutable*/, + Density.DEFAULT_DENSITY /*density*/); + } + + @LayoutlibDelegate + /*package*/ static void nativePrepareToDraw(int nativeBitmap) { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.mHasAlpha = hasAlpha; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSameAs(int nb0, int nb1) { + Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); + if (delegate1 == null) { + return false; + } + + Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); + if (delegate2 == null) { + return false; + } + + BufferedImage image1 = delegate1.getImage(); + BufferedImage image2 = delegate2.getImage(); + if (delegate1.mConfig != delegate2.mConfig || + image1.getWidth() != image2.getWidth() || + image1.getHeight() != image2.getHeight()) { + return false; + } + + // get the internal data + int w = image1.getWidth(); + int h = image2.getHeight(); + int[] argb1 = new int[w*h]; + int[] argb2 = new int[w*h]; + + image1.getRGB(0, 0, w, h, argb1, 0, w); + image2.getRGB(0, 0, w, h, argb2, 0, w); + + // compares + if (delegate1.mConfig == Config.ALPHA_8) { + // in this case we have to manually compare the alpha channel as the rest is garbage. + final int length = w*h; + for (int i = 0 ; i < length ; i++) { + if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { + return false; + } + } + return true; + } + + return Arrays.equals(argb1, argb2); + } + + // ---- Private delegate/helper methods ---- + + private Bitmap_Delegate(BufferedImage image, Config config) { + mImage = image; + mConfig = config; + } + + private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { + // get its native_int + int nativeInt = sManager.addNewDelegate(delegate); + + // and create/return a new Bitmap with it + return new Bitmap(nativeInt, isMutable, null /*ninePatchChunk*/, density); + } + + /** + * Creates and returns a copy of a given BufferedImage. + * <p/> + * if alpha is different than 255, then it is applied to the alpha channel of each pixel. + * + * @param image the image to copy + * @param imageType the type of the new image + * @param alpha an optional alpha modifier + * @return a new BufferedImage + */ + /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { + int w = image.getWidth(); + int h = image.getHeight(); + + BufferedImage result = new BufferedImage(w, h, imageType); + + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + if (alpha != 255) { + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + int a = (argb[i] >>> 24 * alpha) / 255; + argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); + } + } + + result.setRGB(0, 0, w, h, argb, 0, w); + + return result; + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java new file mode 100644 index 0000000..4becba1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.BlurMaskFilter + * + * Through the layoutlib_create tool, the original native methods of BlurMaskFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original BlurMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class BlurMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Blur Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float radius, int style) { + BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java deleted file mode 100644 index d5d315e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ /dev/null @@ -1,1279 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.api.ILayoutLog; - -import android.graphics.DrawFilter; -import android.graphics.Picture; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Xfermode; -import android.graphics.Paint.Align; -import android.graphics.Paint.FontInfo; -import android.graphics.Paint.Style; -import android.graphics.Region.Op; - -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.util.List; -import java.util.Stack; - -import javax.microedition.khronos.opengles.GL; - -/** - * Re-implementation of the Canvas, 100% in java on top of a BufferedImage. - */ -public class Canvas extends _Original_Canvas { - - private BufferedImage mBufferedImage; - private final Stack<Graphics2D> mGraphicsStack = new Stack<Graphics2D>(); - private final ILayoutLog mLogger; - - public Canvas() { - mLogger = null; - // the mBufferedImage will be taken from a bitmap in #setBitmap() - } - - public Canvas(Bitmap bitmap) { - mLogger = null; - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int nativeCanvas) { - mLogger = null; - throw new UnsupportedOperationException("Can't create Canvas(int)"); - } - - public Canvas(javax.microedition.khronos.opengles.GL gl) { - mLogger = null; - throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)"); - } - - // custom constructors for our use. - public Canvas(int width, int height, ILayoutLog logger) { - mLogger = logger; - mBufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - public Canvas(int width, int height) { - this(width, height, null /* logger*/); - } - - // custom mehtods - public BufferedImage getImage() { - return mBufferedImage; - } - - public Graphics2D getGraphics2d() { - return mGraphicsStack.peek(); - } - - public void dispose() { - while (mGraphicsStack.size() > 0) { - mGraphicsStack.pop().dispose(); - } - } - - /** - * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. - * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. - */ - private Graphics2D getCustomGraphics(Paint paint) { - // make new one - Graphics2D g = getGraphics2d(); - g = (Graphics2D)g.create(); - - // configure it - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - - Style style = paint.getStyle(); - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - PathEffect e = paint.getPathEffect(); - if (e instanceof DashPathEffect) { - DashPathEffect dpe = (DashPathEffect)e; - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter(), - dpe.getIntervals(), - dpe.getPhase())); - } else { - g.setStroke(new BasicStroke( - paint.getStrokeWidth(), - paint.getStrokeCap().getJavaCap(), - paint.getStrokeJoin().getJavaJoin(), - paint.getStrokeMiter())); - } - } - - Xfermode xfermode = paint.getXfermode(); - if (xfermode instanceof PorterDuffXfermode) { - PorterDuff.Mode mode = ((PorterDuffXfermode)xfermode).getMode(); - - setModeInGraphics(mode, g, falpha); - } else { - if (mLogger != null && xfermode != null) { - mLogger.warning(String.format( - "Xfermode '%1$s' is not supported in the Layout Editor.", - xfermode.getClass().getCanonicalName())); - } - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - } - - Shader shader = paint.getShader(); - if (shader != null) { - java.awt.Paint shaderPaint = shader.getJavaPaint(); - if (shaderPaint != null) { - g.setPaint(shaderPaint); - } else { - if (mLogger != null) { - mLogger.warning(String.format( - "Shader '%1$s' is not supported in the Layout Editor.", - shader.getClass().getCanonicalName())); - } - } - } - - return g; - } - - private void setModeInGraphics(PorterDuff.Mode mode, Graphics2D g, float falpha) { - switch (mode) { - case CLEAR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha)); - break; - case DARKEN: - break; - case DST: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST, falpha)); - break; - case DST_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha)); - break; - case DST_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha)); - break; - case DST_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha)); - break; - case DST_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha)); - break; - case LIGHTEN: - break; - case MULTIPLY: - break; - case SCREEN: - break; - case SRC: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, falpha)); - break; - case SRC_ATOP: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha)); - break; - case SRC_IN: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha)); - break; - case SRC_OUT: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha)); - break; - case SRC_OVER: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - break; - case XOR: - g.setComposite(AlphaComposite.getInstance(AlphaComposite.XOR, falpha)); - break; - } - } - - - // -------------------- - // OVERRIDEN ENUMS - // This is needed since we rename Canvas into _Original_Canvas - // -------------------- - - public enum EdgeType { - BW(0), //!< treat edges by just rounding to nearest pixel boundary - AA(1); //!< treat edges by rounding-out, since they may be antialiased - - EdgeType(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - - // -------------------- - // OVERRIDEN METHODS - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#setBitmap(android.graphics.Bitmap) - */ - @Override - public void setBitmap(Bitmap bitmap) { - mBufferedImage = bitmap.getImage(); - mGraphicsStack.push(mBufferedImage.createGraphics()); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#translate(float, float) - */ - @Override - public void translate(float dx, float dy) { - getGraphics2d().translate(dx, dy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save() - */ - @Override - public int save() { - // get the current save count - int count = mGraphicsStack.size(); - - // create a new graphics and add it to the stack - Graphics2D g = (Graphics2D)getGraphics2d().create(); - mGraphicsStack.push(g); - - // return the old save count - return count; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#save(int) - */ - @Override - public int save(int saveFlags) { - // For now we ignore saveFlags - return save(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restore() - */ - @Override - public void restore() { - mGraphicsStack.pop(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#restoreToCount(int) - */ - @Override - public void restoreToCount(int saveCount) { - while (mGraphicsStack.size() > saveCount) { - mGraphicsStack.pop(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getSaveCount() - */ - @Override - public int getSaveCount() { - return mGraphicsStack.size(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom, Op op) { - return clipRect(left, top, right, bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(float, float, float, float) - */ - @Override - public boolean clipRect(float left, float top, float right, float bottom) { - getGraphics2d().clipRect((int)left, (int)top, (int)(right-left), (int)(bottom-top)); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(int, int, int, int) - */ - @Override - public boolean clipRect(int left, int top, int right, int bottom) { - getGraphics2d().clipRect(left, top, right-left, bottom-top); - return true; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(Rect rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.Rect) - */ - @Override - public boolean clipRect(Rect rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF, android.graphics.Region.Op) - */ - @Override - public boolean clipRect(RectF rect, Op op) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRect(android.graphics.RectF) - */ - @Override - public boolean clipRect(RectF rect) { - return clipRect(rect.left, rect.top, rect.right, rect.bottom); - } - - public boolean quickReject(RectF rect, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(RectF rect, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(Path path, EdgeType type) { - return false; - } - - @Override - public boolean quickReject(Path path, _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public boolean quickReject(float left, float top, float right, float bottom, - EdgeType type) { - return false; - } - - @Override - public boolean quickReject(float left, float top, float right, float bottom, - _Original_Canvas.EdgeType type) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Retrieve the clip bounds, returning true if they are non-empty. - * - * @param bounds Return the clip bounds here. If it is null, ignore it but - * still return true if the current clip is non-empty. - * @return true if the current clip is non-empty. - */ - @Override - public boolean getClipBounds(Rect bounds) { - Rectangle rect = getGraphics2d().getClipBounds(); - if (rect != null) { - bounds.left = rect.x; - bounds.top = rect.y; - bounds.right = rect.x + rect.width; - bounds.bottom = rect.y + rect.height; - return true; - } - return false; - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int, android.graphics.PorterDuff.Mode) - */ - @Override - public void drawColor(int color, PorterDuff.Mode mode) { - Graphics2D g = getGraphics2d(); - - // save old color - Color c = g.getColor(); - - Composite composite = g.getComposite(); - - // get the alpha from the color - int alpha = color >>> 24; - float falpha = alpha / 255.f; - - setModeInGraphics(mode, g, falpha); - - g.setColor(new Color(color)); - - g.fillRect(0, 0, getWidth(), getHeight()); - - g.setComposite(composite); - - // restore color - g.setColor(c); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawColor(int) - */ - @Override - public void drawColor(int color) { - drawColor(color, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawARGB(int, int, int, int) - */ - @Override - public void drawARGB(int a, int r, int g, int b) { - drawColor(a << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRGB(int, int, int) - */ - @Override - public void drawRGB(int r, int g, int b) { - drawColor(0xFF << 24 | r << 16 | g << 8 | b, PorterDuff.Mode.SRC_OVER); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#getWidth() - */ - @Override - public int getWidth() { - return mBufferedImage.getWidth(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getHeight() - */ - @Override - public int getHeight() { - return mBufferedImage.getHeight(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPaint(android.graphics.Paint) - */ - @Override - public void drawPaint(Paint paint) { - drawColor(paint.getColor()); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, float, float, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)left, (int)top, - (int)left+bitmap.getWidth(), (int)top+bitmap.getHeight(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Matrix, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - boolean needsRestore = false; - if (matrix.isIdentity() == false) { - // create a new graphics and apply the matrix to it - save(); // this creates a new Graphics2D, and stores it for children call to use - needsRestore = true; - Graphics2D g = getGraphics2d(); // get the newly create Graphics2D - - // get the Graphics2D current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform from the matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - // draw the bitmap - drawBitmap(bitmap, 0, 0, paint); - - if (needsRestore) { - // remove the new graphics - restore(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - dst.left, dst.top, dst.right, dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(android.graphics.Bitmap, android.graphics.Rect, android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - if (src == null) { - drawBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } else { - drawBitmap(bitmap, src.left, src.top, src.width(), src.height(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom, paint); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmap(int[], int, int, int, int, int, int, boolean, android.graphics.Paint) - */ - @Override - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, - int height, boolean hasAlpha, Paint paint) { - throw new UnsupportedOperationException(); - } - - private void drawBitmap(Bitmap bitmap, int sleft, int stop, int sright, int sbottom, int dleft, - int dtop, int dright, int dbottom, Paint paint) { - BufferedImage image = bitmap.getImage(); - - Graphics2D g = getGraphics2d(); - - Composite c = null; - - if (paint != null) { - if (paint.isFilterBitmap()) { - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BILINEAR); - } - - if (paint.getAlpha() != 0xFF) { - c = g.getComposite(); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, - paint.getAlpha()/255.f)); - } - } - - g.drawImage(image, dleft, dtop, dright, dbottom, - sleft, stop, sright, sbottom, null); - - if (paint != null) { - if (paint.isFilterBitmap()) { - g.dispose(); - } - if (c != null) { - g.setComposite(c); - } - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float, float, float) - */ - @Override - public void rotate(float degrees, float px, float py) { - if (degrees != 0) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.rotate(Math.toRadians(degrees)); - g.translate(-px, -py); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#rotate(float) - */ - @Override - public void rotate(float degrees) { - getGraphics2d().rotate(Math.toRadians(degrees)); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float, float, float) - */ - @Override - public void scale(float sx, float sy, float px, float py) { - Graphics2D g = getGraphics2d(); - g.translate(px, py); - g.scale(sx, sy); - g.translate(-px, -py); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#scale(float, float) - */ - @Override - public void scale(float sx, float sy) { - getGraphics2d().scale(sx, sy); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(char[], int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { - // WARNING: the logic in this method is similar to Paint.measureText. - // Any change to this method should be reflected in Paint.measureText - Graphics2D g = getGraphics2d(); - - g = (Graphics2D)g.create(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // set the color. because this only handles RGB, the alpha channel is handled - // as a composite. - g.setColor(new Color(paint.getColor())); - int alpha = paint.getAlpha(); - float falpha = alpha / 255.f; - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); - - - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - if (paint.getTextAlign() != Align.LEFT) { - float m = paint.measureText(text, index, count); - if (paint.getTextAlign() == Align.CENTER) { - x -= m / 2; - } else if (paint.getTextAlign() == Align.RIGHT) { - x -= m; - } - } - - List<FontInfo> fonts = paint.getFonts(); - try { - 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. - g.setFont(mainFont.mFont); - g.drawChars(text, i, lastIndex - i, (int)x, (int)y); - return; - } else if (upTo > 0) { - // draw what's possible - g.setFont(mainFont.mFont); - g.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 - g.setFont(fontInfo.mFont); - g.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; - - g.setFont(mainFont.mFont); - g.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; - } - } - } - } finally { - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - drawText(text.toString().toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, float x, float y, Paint paint) { - drawText(text.toCharArray(), 0, text.length(), x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawText(java.lang.String, int, int, float, float, android.graphics.Paint) - */ - @Override - public void drawText(String text, int start, int end, float x, float y, Paint paint) { - drawText(text.toCharArray(), start, end - start, x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawRect(RectF rect, Paint paint) { - doDrawRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - doDrawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top), paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRect(android.graphics.Rect, android.graphics.Paint) - */ - @Override - public void drawRect(Rect r, Paint paint) { - doDrawRect(r.left, r.top, r.width(), r.height(), paint); - } - - private final void doDrawRect(int left, int top, int width, int height, Paint paint) { - if (width > 0 && height > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRect(left, top, width, height); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRect(left, top, width, height); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawRoundRect(android.graphics.RectF, float, float, android.graphics.Paint) - */ - @Override - public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { - if (rect.width() > 0 && rect.height() > 0) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - - int arcWidth = (int)(rx * 2); - int arcHeight = (int)(ry * 2); - - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(), - arcWidth, arcHeight); - } - - // dispose Graphics2D object - g.dispose(); - } - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLine(float, float, float, float, android.graphics.Paint) - */ - @Override - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, int offset, int count, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - for (int i = 0 ; i < count ; i += 4) { - g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], - (int)pts[i + offset + 2], (int)pts[i + offset + 3]); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawLines(float[], android.graphics.Paint) - */ - @Override - public void drawLines(float[] pts, Paint paint) { - drawLines(pts, 0, pts.length, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawCircle(float, float, float, android.graphics.Paint) - */ - @Override - public void drawCircle(float cx, float cy, float radius, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - int size = (int)(radius * 2); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)(cx - radius), (int)(cy - radius), size, size); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawOval(android.graphics.RectF, android.graphics.Paint) - */ - @Override - public void drawOval(RectF oval, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPath(android.graphics.Path, android.graphics.Paint) - */ - @Override - public void drawPath(Path path, Paint paint) { - // get a Graphics2D object configured with the drawing parameters. - Graphics2D g = getCustomGraphics(paint); - - Style style = paint.getStyle(); - - // draw - if (style == Style.FILL || style == Style.FILL_AND_STROKE) { - g.fill(path.getAwtShape()); - } - - if (style == Style.STROKE || style == Style.FILL_AND_STROKE) { - g.draw(path.getAwtShape()); - } - - // dispose Graphics2D object - g.dispose(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setMatrix(android.graphics.Matrix) - */ - @Override - public void setMatrix(Matrix matrix) { - // get the new current graphics - Graphics2D g = getGraphics2d(); - - // and apply the matrix - g.setTransform(matrix.getTransform()); - - if (mLogger != null && matrix.hasPerspective()) { - mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor."); - } - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#concat(android.graphics.Matrix) - */ - @Override - public void concat(Matrix matrix) { - // get the current top graphics2D object. - Graphics2D g = getGraphics2d(); - - // get its current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform of the given matrix - AffineTransform matrixTx = matrix.getTransform(); - - // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics2D as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - - // -------------------- - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path, android.graphics.Region.Op) - */ - @Override - public boolean clipPath(Path path, Op op) { - // TODO Auto-generated method stub - return super.clipPath(path, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipPath(android.graphics.Path) - */ - @Override - public boolean clipPath(Path path) { - // TODO Auto-generated method stub - return super.clipPath(path); - } - - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region, android.graphics.Region.Op) - */ - @Override - public boolean clipRegion(Region region, Op op) { - // TODO Auto-generated method stub - return super.clipRegion(region, op); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#clipRegion(android.graphics.Region) - */ - @Override - public boolean clipRegion(Region region) { - // TODO Auto-generated method stub - return super.clipRegion(region); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint) - */ - @Override - public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, - Paint paint) { - // TODO Auto-generated method stub - super.drawArc(oval, startAngle, sweepAngle, useCenter, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawBitmapMesh(android.graphics.Bitmap, int, int, float[], int, int[], int, android.graphics.Paint) - */ - @Override - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, - int vertOffset, int[] colors, int colorOffset, Paint paint) { - // TODO Auto-generated method stub - super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.Rect) - */ - @Override - public void drawPicture(Picture picture, Rect dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture, android.graphics.RectF) - */ - @Override - public void drawPicture(Picture picture, RectF dst) { - // TODO Auto-generated method stub - super.drawPicture(picture, dst); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPicture(android.graphics.Picture) - */ - @Override - public void drawPicture(Picture picture) { - // TODO Auto-generated method stub - super.drawPicture(picture); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoint(float, float, android.graphics.Paint) - */ - @Override - public void drawPoint(float x, float y, Paint paint) { - // TODO Auto-generated method stub - super.drawPoint(x, y, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], int, int, android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, offset, count, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPoints(float[], android.graphics.Paint) - */ - @Override - public void drawPoints(float[] pts, Paint paint) { - // TODO Auto-generated method stub - super.drawPoints(pts, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(char[], int, int, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, index, count, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawPosText(java.lang.String, float[], android.graphics.Paint) - */ - @Override - public void drawPosText(String text, float[] pos, Paint paint) { - // TODO Auto-generated method stub - super.drawPosText(text, pos, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(char[], int, int, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(char[] text, int index, int count, Path path, float offset, - float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, index, count, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawTextOnPath(java.lang.String, android.graphics.Path, float, float, android.graphics.Paint) - */ - @Override - public void drawTextOnPath(String text, Path path, float offset, float offset2, Paint paint) { - // TODO Auto-generated method stub - super.drawTextOnPath(text, path, offset, offset2, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, float[], int, float[], int, int[], int, short[], int, int, android.graphics.Paint) - */ - @Override - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, - int indexOffset, int indexCount, Paint paint) { - // TODO Auto-generated method stub - super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, - indices, indexOffset, indexCount, paint); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getDrawFilter() - */ - @Override - public DrawFilter getDrawFilter() { - // TODO Auto-generated method stub - return super.getDrawFilter(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getGL() - */ - @Override - public GL getGL() { - // TODO Auto-generated method stub - return super.getGL(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getMatrix() - */ - @Override - public Matrix getMatrix() { - // TODO Auto-generated method stub - return super.getMatrix(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#getMatrix(android.graphics.Matrix) - */ - @Override - public void getMatrix(Matrix ctm) { - // TODO Auto-generated method stub - super.getMatrix(ctm); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#isOpaque() - */ - @Override - public boolean isOpaque() { - // TODO Auto-generated method stub - return super.isOpaque(); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(float, float, float, float, android.graphics.Paint, int) - */ - @Override - public int saveLayer(float left, float top, float right, float bottom, Paint paint, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(left, top, right, bottom, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayer(android.graphics.RectF, android.graphics.Paint, int) - */ - @Override - public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayer(bounds, paint, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(float, float, float, float, int, int) - */ - @Override - public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(left, top, right, bottom, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#saveLayerAlpha(android.graphics.RectF, int, int) - */ - @Override - public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { - // TODO Auto-generated method stub - return super.saveLayerAlpha(bounds, alpha, saveFlags); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setDrawFilter(android.graphics.DrawFilter) - */ - @Override - public void setDrawFilter(DrawFilter filter) { - // TODO Auto-generated method stub - super.setDrawFilter(filter); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#setViewport(int, int) - */ - @Override - public void setViewport(int width, int height) { - // TODO Auto-generated method stub - super.setViewport(width, height); - } - - /* (non-Javadoc) - * @see android.graphics.Canvas#skew(float, float) - */ - @Override - public void skew(float sx, float sy) { - // TODO Auto-generated method stub - super.skew(sx, sy); - } - - - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java new file mode 100644 index 0000000..02a2ddf --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -0,0 +1,1334 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +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; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.List; + + +/** + * Delegate implementing the native methods of android.graphics.Canvas + * + * Through the layoutlib_create tool, the original native methods of Canvas have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Canvas class. + * + * @see DelegateManager + * + */ +public final class Canvas_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Canvas_Delegate> sManager = + new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); + + // ---- delegate helper data ---- + + private final static boolean[] sBoolOut = new boolean[1]; + + // ---- delegate data ---- + private Bitmap_Delegate mBitmap; + private GcSnapshot mSnapshot; + + private DrawFilter_Delegate mDrawFilter = null; + + // ---- Public Helper methods ---- + + /** + * Returns the native delegate associated to a given {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(Canvas canvas) { + return sManager.getDelegate(canvas.mNativeCanvas); + } + + /** + * Returns the native delegate associated to a given an int referencing a {@link Canvas} object. + */ + public static Canvas_Delegate getDelegate(int native_canvas) { + return sManager.getDelegate(native_canvas); + } + + /** + * Returns the current {@link Graphics2D} used to draw. + */ + public GcSnapshot getSnapshot() { + return mSnapshot; + } + + /** + * Returns the {@link DrawFilter} delegate or null if none have been set. + * + * @return the delegate or null. + */ + public DrawFilter_Delegate getDrawFilter() { + return mDrawFilter; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isOpaque(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; + } + + @LayoutlibDelegate + /*package*/ static int getWidth(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getWidth(); + } + + @LayoutlibDelegate + /*package*/ static int getHeight(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.mBitmap.getImage().getHeight(); + } + + @LayoutlibDelegate + /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().translate(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void rotate(Canvas thisCanvas, float degrees) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); + } + + @LayoutlibDelegate + /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().scale(sx, sy); + } + + @LayoutlibDelegate + /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot g = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform for the given skew. + float[] mtx = Matrix_Delegate.getSkew(kx, ky); + AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) { + return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) { + return clipRect(thisCanvas, (float) rect.left, (float) rect.top, + (float) rect.right, (float) rect.bottom); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, + float bottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right, + int bottom) { + + return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas) { + return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); + } + + @LayoutlibDelegate + /*package*/ static int save(Canvas thisCanvas, int saveFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.save(saveFlags); + } + + @LayoutlibDelegate + /*package*/ static void restore(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restore(); + } + + @LayoutlibDelegate + /*package*/ static int getSaveCount(Canvas thisCanvas) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.getSnapshot().size(); + } + + @LayoutlibDelegate + /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.restoreTo(saveCount); + } + + @LayoutlibDelegate + /*package*/ static void drawText(Canvas thisCanvas, + String text, float x, float y, Paint paint) { + native_drawText(thisCanvas.mNativeCanvas, text, 0, text.length(), x, y, + paint.mNativePaint); + } + + @LayoutlibDelegate + /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count, + Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void drawLines(Canvas thisCanvas, + final float[] pts, final int offset, final int count, + Paint paint) { + draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + for (int i = 0 ; i < count ; i += 4) { + graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], + (int)pts[i + offset + 2], (int)pts[i + offset + 3]); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void freeCaches() { + // nothing to be done here. + } + + @LayoutlibDelegate + /*package*/ static int initRaster(int nativeBitmapOrZero) { + if (nativeBitmapOrZero > 0) { + // get the Bitmap from the int + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); + + // create a new Canvas_Delegate with the given bitmap and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); + + return sManager.addNewDelegate(newDelegate); + } else { + // create a new Canvas_Delegate and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + } + + @LayoutlibDelegate + /*package*/ static int initGL() { + // not supported. + return 0; + } + + @LayoutlibDelegate + /*package*/ static void native_setBitmap(int nativeCanvas, int bitmap) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + canvasDelegate.setBitmap(bitmapDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetViewport(int nCanvas, int w, int h) { + // only useful in GL which is not supported, so no need to do anything. + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayer(int nativeCanvas, float l, + float t, float r, float b, + int paint, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(new RectF(l, t, r, b), + paintDelegate, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, + RectF bounds, int alpha, + int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags); + } + + @LayoutlibDelegate + /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l, + float t, float r, float b, + int alpha, int layerFlags) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); + } + + + @LayoutlibDelegate + /*package*/ static void native_concat(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get its current matrix + AffineTransform currentTx = snapshot.getTransform(); + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(currentTx); + } + + @LayoutlibDelegate + /*package*/ static void native_setMatrix(int nCanvas, int nMatrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + // get the current top graphics2D object. + GcSnapshot snapshot = canvasDelegate.getSnapshot(); + + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrixDelegate.getAffineTransform(); + + // give it to the graphics2D as a new matrix replacing all previous transform + snapshot.setTransform(matrixTx); + + if (matrixDelegate.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " + + "supports affine transformations.", null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRect(int nCanvas, + float left, float top, + float right, float bottom, + int regionOp) { + + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return false; + } + + return canvasDelegate.clipRect(left, top, right, bottom, regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipPath(int nativeCanvas, + int nativePath, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath); + if (pathDelegate == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static boolean native_clipRegion(int nativeCanvas, + int nativeRegion, + int regionOp) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return true; + } + + Region_Delegate region = Region_Delegate.getDelegate(nativeRegion); + if (region == null) { + return true; + } + + return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp); + } + + @LayoutlibDelegate + /*package*/ static void nativeSetDrawFilter(int nativeCanvas, int nativeFilter) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter); + + if (canvasDelegate.mDrawFilter != null && + canvasDelegate.mDrawFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER, + canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_getClipBounds(int nativeCanvas, + Rect bounds) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return false; + } + + Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds(); + if (rect != null && rect.isEmpty() == false) { + bounds.left = rect.x; + bounds.top = rect.y; + bounds.right = rect.x + rect.width; + bounds.bottom = rect.y + rect.height; + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_getCTM(int canvas, int matrix) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + if (canvasDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + AffineTransform transform = canvasDelegate.getSnapshot().getTransform(); + matrixDelegate.set(Matrix_Delegate.makeValues(transform)); + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + RectF rect, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + int path, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_quickReject(int nativeCanvas, + float left, float top, + float right, float bottom, + int native_edgeType) { + // FIXME properly implement quickReject + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_drawRGB(int nativeCanvas, int r, int g, int b) { + native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + + } + + @LayoutlibDelegate + /*package*/ static void native_drawARGB(int nativeCanvas, int a, int r, int g, int b) { + native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF), + PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, int color) { + native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt); + } + + @LayoutlibDelegate + /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + final int w = canvasDelegate.mBitmap.getImage().getWidth(); + final int h = canvasDelegate.mBitmap.getImage().getHeight(); + draw(nativeCanvas, new GcSnapshot.Drawable() { + + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // reset its transform just in case + graphics.setTransform(new AffineTransform()); + + // set the color + graphics.setColor(new Color(color, true /*alpha*/)); + + Composite composite = PorterDuffXfermode_Delegate.getComposite( + PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF); + if (composite != null) { + graphics.setComposite(composite); + } + + graphics.fillRect(0, 0, w, h); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPaint(int nativeCanvas, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPaint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawLine(int nativeCanvas, + final float startX, final float startY, final float stopX, final float stopY, + int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, RectF rect, + int paint) { + native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRect(int nativeCanvas, + final float left, final float top, final float right, final float bottom, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRect((int)left, (int)top, + (int)(right-left), (int)(bottom-top)); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) { + if (oval.right > oval.left && oval.bottom > oval.top) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawOval((int)oval.left, (int)oval.top, + (int)oval.width(), (int)oval.height()); + } + } + }); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawCircle(int nativeCanvas, + float cx, float cy, float radius, int paint) { + native_drawOval(nativeCanvas, + new RectF(cx - radius, cy - radius, radius*2, radius*2), + paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawArc(int nativeCanvas, + RectF oval, float startAngle, float sweep, boolean useCenter, int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawRoundRect(int nativeCanvas, + final RectF rect, final float rx, final float ry, int paint) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + int style = paint.getStyle(); + + // draw + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fillRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.drawRoundRect( + (int)rect.left, (int)rect.top, + (int)rect.width(), (int)rect.height(), + (int)rx, (int)ry); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPath(int nativeCanvas, int path, int paint) { + final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path); + if (pathDelegate == null) { + return; + } + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + Shape shape = pathDelegate.getJavaShape(); + int style = paint.getStyle(); + + if (style == Paint.Style.FILL.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.fill(shape); + } + + if (style == Paint.Style.STROKE.nativeInt || + style == Paint.Style.FILL_AND_STROKE.nativeInt) { + graphics.draw(shape); + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + float left, float top, + int nativePaintOrZero, + int canvasDensity, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + float right = left + image.getWidth(); + float bottom = top + image.getHeight(); + + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)left, (int)top, (int)right, (int)bottom); + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap, + Rect src, RectF dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int bitmap, + Rect src, Rect dst, + int nativePaintOrZero, + int screenDensity, + int bitmapDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (bitmapDelegate == null) { + return; + } + + BufferedImage image = bitmapDelegate.getImage(); + + if (src == null) { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + 0, 0, image.getWidth(), image.getHeight(), + dst.left, dst.top, dst.right, dst.bottom); + } else { + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + src.left, src.top, src.width(), src.height(), + dst.left, dst.top, dst.right, dst.bottom); + } + } + + @LayoutlibDelegate + /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors, + int offset, int stride, final float x, + final float y, int width, int height, + boolean hasAlpha, + int nativePaintOrZero) { + + // create a temp BufferedImage containing the content. + final BufferedImage image = new BufferedImage(width, height, + hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, width, height, colors, offset, stride); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + graphics.drawImage(image, (int) x, (int) y, null); + } + }); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap, + int nMatrix, int nPaint) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the delegate from the native int, which can be null + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + // get the delegate from the native int. + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap); + if (bitmapDelegate == null) { + return; + } + + final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut); + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix); + if (matrixDelegate == null) { + return; + } + + final AffineTransform mtx = matrixDelegate.getAffineTransform(); + + canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, mtx, null); + } + }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap, + int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, + int colorOffset, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawBitmapMesh is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeDrawVertices(int nCanvas, int mode, int n, + float[] verts, int vertOffset, + float[] texs, int texOffset, + int[] colors, int colorOffset, + short[] indices, int indexOffset, + int indexCount, int nPaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawVertices is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, + final char[] text, final int index, final int count, + final float startX, final float startY, int paint) { + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + // 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; + if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) { + float m = paint.measureText(text, index, count); + if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + List<FontInfo> fonts = paint.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; + } + } + } + } + }); + } + + @LayoutlibDelegate + /*package*/ static void native_drawText(int nativeCanvas, String text, + int start, int end, float x, + float y, int paint) { + int count = end - start; + char[] buffer = TemporaryBuffer.obtain(count); + TextUtils.getChars(text, start, end, buffer, 0); + + native_drawText(nativeCanvas, buffer, 0, count, x, y, paint); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + char[] text, int index, + int count, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPosText(int nativeCanvas, + String text, float[] pos, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPosText is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + char[] text, int index, + int count, int path, + float hOffset, + float vOffset, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawTextOnPath(int nativeCanvas, + String text, int path, + float hOffset, + float vOffset, + int paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawTextOnPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPicture(int nativeCanvas, + int nativePicture) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPicture is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativeCanvas) { + // get the delegate from the native int so that it can be disposed. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.dispose(); + + // remove it from the manager. + sManager.removeJavaReferenceFor(nativeCanvas); + } + + // ---- Private delegate/helper methods ---- + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, int nPaint, boolean compositeOnly, boolean forceSrcMode, + GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint which can be null if nPaint is 0; + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint); + + canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode); + } + + /** + * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided + * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. + * <p>Note that the drawable may actually be executed several times if there are + * layers involved (see {@link #saveLayer(RectF, int, int)}. + */ + private static void draw(int nCanvas, GcSnapshot.Drawable drawable) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.mSnapshot.draw(drawable); + } + + private Canvas_Delegate(Bitmap_Delegate bitmap) { + mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); + } + + private Canvas_Delegate() { + mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/); + } + + /** + * Disposes of the {@link Graphics2D} stack. + */ + private void dispose() { + mSnapshot.dispose(); + } + + private int save(int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.save(saveFlags); + + // return the old save count + return count; + } + + private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) { + Paint_Delegate paint = new Paint_Delegate(); + paint.setAlpha(alpha); + return saveLayer(rect, paint, saveFlags); + } + + private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) { + // get the current save count + int count = mSnapshot.size(); + + mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags); + + // return the old save count + return count; + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restoreTo(int saveCount) { + mSnapshot = mSnapshot.restoreTo(saveCount); + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var> + * @param saveCount the saveCount + */ + private void restore() { + mSnapshot = mSnapshot.restore(); + } + + private boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return mSnapshot.clipRect(left, top, right, bottom, regionOp); + } + + private void setBitmap(Bitmap_Delegate bitmap) { + mBitmap = bitmap; + assert mSnapshot.size() == 1; + mSnapshot.setBitmap(mBitmap); + } + + private static void drawBitmap( + int nativeCanvas, + Bitmap_Delegate bitmap, + int nativePaintOrZero, + final int sleft, final int stop, final int sright, final int sbottom, + final int dleft, final int dtop, final int dright, final int dbottom) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + // get the paint, which could be null if the int is 0 + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero); + + final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut); + + draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0], + new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + if (paint != null && paint.isFilterBitmap()) { + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + //FIXME add support for canvas, screen and bitmap densities. + graphics.drawImage(image, dleft, dtop, dright, dbottom, + sleft, stop, sright, sbottom, null); + } + }); + } + + + /** + * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate. + * The image returns, through a 1-size boolean array, whether the drawing code should + * use a SRC composite no matter what the paint says. + * + * @param bitmap the bitmap + * @param paint the paint that will be used to draw + * @param forceSrcMode whether the composite will have to be SRC + * @return the image to draw + */ + private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint, + boolean[] forceSrcMode) { + BufferedImage image = bitmap.getImage(); + forceSrcMode[0] = false; + + // if the bitmap config is alpha_8, then we erase all color value from it + // before drawing it. + if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) { + fixAlpha8Bitmap(image); + } else if (bitmap.hasAlpha() == false) { + // hasAlpha is merely a rendering hint. There can in fact be alpha values + // in the bitmap but it should be ignored at drawing time. + // There is two ways to do this: + // - override the composite to be SRC. This can only be used if the composite + // was going to be SRC or SRC_OVER in the first place + // - Create a different bitmap to draw in which all the alpha channel values is set + // to 0xFF. + if (paint != null) { + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) { + PorterDuff.Mode mode = + ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode(); + + forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || + mode == PorterDuff.Mode.SRC; + } + } + + // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB + if (forceSrcMode[0] == false) { + image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF); + } + } + + return image; + } + + private static void fixAlpha8Bitmap(final BufferedImage image) { + int w = image.getWidth(); + int h = image.getHeight(); + int[] argb = new int[w * h]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); + + final int length = argb.length; + for (int i = 0 ; i < length; i++) { + argb[i] &= 0xFF000000; + } + image.setRGB(0, 0, w, h, argb, 0, w); + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java new file mode 100644 index 0000000..4c692c2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ColorFilter class. + * + * This also serve as a base class for all ColorFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class ColorFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<ColorFilter_Delegate> sManager = + new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static ColorFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java new file mode 100644 index 0000000..d4c5b8d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter + * + * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ColorMatrixColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "ColorMatrix Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeColorMatrixFilter(float[] array) { + ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java new file mode 100644 index 0000000..7c04a87 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.ComposePathEffect + * + * Through the layoutlib_create tool, the original native methods of ComposePathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ComposePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class ComposePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int outerpe, int innerpe) { + ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java deleted file mode 100644 index 863d64a..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2008 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.Paint; - -/** A subclass of shader that returns the composition of two other shaders, combined by - an {@link android.graphics.Xfermode} subclass. -*/ -public class ComposeShader extends Shader { - /** Create a new compose shader, given shaders A, B, and a combining mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The mode that combines the colors from the two shaders. If mode - is null, then SRC_OVER is assumed. - */ - public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) { - // FIXME Implement shader - } - - /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. - When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". - @param shaderA The colors from this shader are seen as the "dst" by the mode - @param shaderB The colors from this shader are seen as the "src" by the mode - @param mode The PorterDuff mode that combines the colors from the two shaders. - */ - public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) { - // FIXME Implement shader - } - - @Override - Paint getJavaPaint() { - return null; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java new file mode 100644 index 0000000..fcc12d7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Paint; + +/** + * Delegate implementing the native methods of android.graphics.ComposeShader + * + * Through the layoutlib_create tool, the original native methods of ComposeShader have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original ComposeShader class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class ComposeShader_Delegate extends Shader_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Paint getJavaPaint() { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Compose Shaders are not supported in Layout Preview mode."; + } + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(int native_shaderA, int native_shaderB, + int native_mode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(int native_shaderA, int native_shaderB, + int porterDuffMode) { + // FIXME not supported yet. + ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java new file mode 100644 index 0000000..b0f8168 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.CornerPathEffect + * + * Through the layoutlib_create tool, the original native methods of CornerPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original CornerPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class CornerPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Corner Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float radius) { + CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java deleted file mode 100644 index 46d4c70..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2010 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; - -public class DashPathEffect extends PathEffect { - - private final float[] mIntervals; - private final float mPhase; - - /** - * The intervals array must contain an even number of entries (>=2), with - * the even indices specifying the "on" intervals, and the odd indices - * specifying the "off" intervals. phase is an offset into the intervals - * array (mod the sum of all of the intervals). The intervals array - * controls the length of the dashes. The paint's strokeWidth controls the - * thickness of the dashes. - * Note: this patheffect only affects drawing with the paint's style is set - * to STROKE or STROKE_AND_FILL. It is ignored if the drawing is done with - * style == FILL. - * @param intervals array of ON and OFF distances - * @param phase offset into the intervals array - */ - public DashPathEffect(float intervals[], float phase) { - if (intervals.length < 2) { - throw new ArrayIndexOutOfBoundsException(); - } - - mIntervals = intervals; - mPhase = phase; - } - - public float[] getIntervals() { - return mIntervals; - } - - public float getPhase() { - return mPhase; - } -} - diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java new file mode 100644 index 0000000..d97c2ec --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.BasicStroke; +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DashPathEffect + * + * Through the layoutlib_create tool, the original native methods of DashPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public final class DashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + private final float[] mIntervals; + private final float mPhase; + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + return new BasicStroke( + paint.getStrokeWidth(), + paint.getJavaCap(), + paint.getJavaJoin(), + paint.getJavaStrokeMiter(), + mIntervals, + mPhase); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float intervals[], float phase) { + DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private DashPathEffect_Delegate(float intervals[], float phase) { + mIntervals = new float[intervals.length]; + System.arraycopy(intervals, 0, mIntervals, 0, intervals.length); + mPhase = phase; + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java new file mode 100644 index 0000000..ec4a810 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.DiscretePathEffect + * + * Through the layoutlib_create tool, the original native methods of DiscretePathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DiscretePathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class DiscretePathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Discrete Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(float length, float deviation) { + DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java new file mode 100644 index 0000000..870c46b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.DrawFilter + * + * Through the layoutlib_create tool, the original native methods of DrawFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original DrawFilter class. + * + * This also serve as a base class for all DrawFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class DrawFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<DrawFilter_Delegate> sManager = + new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static DrawFilter_Delegate getDelegate(int nativeDrawFilter) { + return sManager.getDelegate(nativeDrawFilter); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int nativeDrawFilter) { + sManager.removeJavaReferenceFor(nativeDrawFilter); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java new file mode 100644 index 0000000..ebc1c1d --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.EmbossMaskFilter + * + * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original EmbossMaskFilter class. + * + * Because this extends {@link MaskFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link MaskFilter_Delegate}. + * + * @see MaskFilter_Delegate + * + */ +public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Emboss Mask Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(float[] direction, float ambient, + float specular, float blurRadius) { + EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java index 8c5a2a4..38c092d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java +++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java @@ -16,23 +16,28 @@ package android.graphics; +import android.graphics.Shader.TileMode; /** - * Base class for Gradient shader. This is not a standard android class and is just used - * as a base class for the re-implemented gradient classes. - * - * It also provides a base class to handle common code between the different shaders' - * implementations of {@link java.awt.Paint}. - * - * @see LinearGradient - * @see RadialGradient - * @see SweepGradient + * Base class for true Gradient shader delegate. */ -public abstract class GradientShader extends Shader { +public abstract class Gradient_Delegate extends Shader_Delegate { protected final int[] mColors; protected final float[] mPositions; + @Override + public boolean isSupported() { + // all gradient shaders are supported. + return true; + } + + @Override + public String getSupportMessage() { + // all gradient shaders are supported, no need for a gradient support + return null; + } + /** * Creates the base shader and do some basic test on the parameters. * @@ -41,7 +46,7 @@ public abstract class GradientShader extends Shader { * corresponding color in the colors array. If this is null, the * the colors are distributed evenly along the gradient line. */ - protected GradientShader(int colors[], float positions[]) { + protected Gradient_Delegate(int colors[], float positions[]) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } @@ -90,7 +95,7 @@ public abstract class GradientShader extends Shader { * Pre-computes the colors for the gradient. This must be called once before any call * to {@link #getGradientColor(float)} */ - protected synchronized void precomputeGradientColors() { + protected void precomputeGradientColors() { if (mGradient == null) { // actually create an array with an extra size, so that we can really go // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0 @@ -126,20 +131,23 @@ public abstract class GradientShader extends Shader { pos = 0.f; break; case REPEAT: - // remove the integer part to stay in the [0,1] range - // careful: this is a negative value, so use ceil instead of floor - pos = pos - (float)Math.ceil(pos); + // remove the integer part to stay in the [0,1] range. + // we also need to invert the value from [-1,0] to [0, 1] + pos = pos - (float)Math.floor(pos); break; case MIRROR: + // this is the same as the positive side, just make the value positive + // first. + pos = Math.abs(pos); + // get the integer and the decimal part - // careful: this is a negative value, so use ceil instead of floor - int intPart = (int)Math.ceil(pos); + int intPart = (int)Math.floor(pos); pos = pos - intPart; - // 0 -> -1 : mirrored order - // -1 -> -2: normal order + // 0 -> 1 : normal order + // 1 -> 2: mirrored // etc.. - // this means if the intpart is even we invert - if ((intPart % 2) == 0) { + // this means if the intpart is odd we invert + if ((intPart % 2) == 1) { pos = 1.f - pos; } break; @@ -199,7 +207,5 @@ public abstract class GradientShader extends Shader { private int computeChannel(int c1, int c2, float percent) { return c1 + (int)((percent * (c2-c1)) + .5); } - - } } diff --git a/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java new file mode 100644 index 0000000..51e0576 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LayerRasterizer + * + * Through the layoutlib_create tool, the original native methods of LayerRasterizer have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LayerRasterizer class. + * + * Because this extends {@link Rasterizer_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link Rasterizer_Delegate}. + * + * @see Rasterizer_Delegate + * + */ +public class LayerRasterizer_Delegate extends Rasterizer_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Layer Rasterizers are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + LayerRasterizer_Delegate newDelegate = new LayerRasterizer_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeAddLayer(int native_layer, int native_paint, float dx, float dy) { + + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java new file mode 100644 index 0000000..3afe965 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.LightingColorFilter + * + * Through the layoutlib_create tool, the original native methods of LightingColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LightingColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class LightingColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Lighting Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreateLightingFilter(int mul, int add) { + LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java deleted file mode 100644 index 10c4a5e..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2008 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; - -public class LinearGradient extends GradientShader { - - private java.awt.Paint mJavaPaint; - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @param colors The colors to be distributed along the gradient line - * @param positions May be null. The relative positions [0..1] of each - * corresponding color in the colors array. If this is null, the - * the colors are distributed evenly along the gradient line. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], - TileMode tile) { - super(colors, positions); - mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); - } - - /** - * Create a shader that draws a linear gradient along a line. - * - * @param x0 The x-coordinate for the start of the gradient line - * @param y0 The y-coordinate for the start of the gradient line - * @param x1 The x-coordinate for the end of the gradient line - * @param y1 The y-coordinate for the end of the gradient line - * @param color0 The color at the start of the gradient line. - * @param color1 The color at the end of the gradient line. - * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, - TileMode tile) { - this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile); - } - - // ---------- Custom Methods - - @Override - java.awt.Paint getJavaPaint() { - return mJavaPaint; - } - - /** - * Linear Gradient (Java) Paint able to handle more than 2 points, as - * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile - * modes. - */ - private static class LinearGradientPaint extends GradientPaint { - - private final float mX0; - private final float mY0; - private final float mDx; - private final float mDy; - private final float mDSize2; - - public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[], - float positions[], TileMode tile) { - super(colors, positions, tile); - mX0 = x0; - mY0 = y0; - mDx = x1 - x0; - mDy = y1 - y0; - mDSize2 = mDx * mDx + mDy * mDy; - } - - public java.awt.PaintContext createContext( - java.awt.image.ColorModel colorModel, - java.awt.Rectangle deviceBounds, - java.awt.geom.Rectangle2D userBounds, - java.awt.geom.AffineTransform xform, - java.awt.RenderingHints hints) { - precomputeGradientColors(); - return new LinearGradientPaintContext(colorModel); - } - - private class LinearGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public LinearGradientPaintContext(java.awt.image.ColorModel colorModel) { - mColorModel = colorModel; - // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix? - } - - public void dispose() { - } - - public java.awt.image.ColorModel getColorModel() { - return mColorModel; - } - - public java.awt.image.Raster getRaster(int x, int y, int w, int h) { - java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, - java.awt.image.BufferedImage.TYPE_INT_ARGB); - - int[] data = new int[w*h]; - - if (mDx == 0) { // vertical gradient - // compute first column and copy to all other columns - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - int color = getColor(iy + y, mY0, mDy); - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = color; - } - } - } else if (mDy == 0) { // horizontal - // compute first line in a tmp array and copy to all lines - int[] line = new int[w]; - for (int ix = 0 ; ix < w ; ix++) { - line[ix] = getColor(ix + x, mX0, mDx); - } - - for (int iy = 0 ; iy < h ; iy++) { - System.arraycopy(line, 0, data, iy*w, line.length); - } - } else { - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - data[index++] = getColor(ix + x, iy + y); - } - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - } - - /** Returns a color for the easy vertical/horizontal mode */ - private int getColor(float absPos, float refPos, float refSize) { - float pos = (absPos - refPos) / refSize; - - return getGradientColor(pos); - } - - /** - * Returns a color for an arbitrary point. - */ - private int getColor(float x, float y) { - // find the x position on the gradient vector. - float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2; - // from it get the position relative to the vector - float pos = (float) ((_x - mX0) / mDx); - - return getGradientColor(pos); - } - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java new file mode 100644 index 0000000..a735ea1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.LinearGradient + * + * Through the layoutlib_create tool, the original native methods of LinearGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original LinearGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public final class LinearGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1( + float x0, float y0, float x1, float y1, + int colors[], float positions[], int tileMode) { + LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(x0, y0, x1, y1, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2( + float x0, float y0, float x1, float y1, + int color0, int color1, int tileMode) { + return nativeCreate1(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, + tileMode); + } + + // ---- Private delegate/helper methods ---- + + /** + * Create a shader that draws a linear gradient along a line. + * + * @param x0 The x-coordinate for the start of the gradient line + * @param y0 The y-coordinate for the start of the gradient line + * @param x1 The x-coordinate for the end of the gradient line + * @param y1 The y-coordinate for the end of the gradient line + * @param colors The colors to be distributed along the gradient line + * @param positions May be null. The relative positions [0..1] of each + * corresponding color in the colors array. If this is null, the + * the colors are distributed evenly along the gradient line. + * @param tile The Shader tiling mode + */ + private LinearGradient_Delegate(float x0, float y0, float x1, float y1, + int colors[], float positions[], TileMode tile) { + super(colors, positions); + mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile); + } + + // ---- Custom Java Paint ---- + /** + * Linear Gradient (Java) Paint able to handle more than 2 points, as + * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile + * modes. + */ + private class LinearGradientPaint extends GradientPaint { + + private final float mX0; + private final float mY0; + private final float mDx; + private final float mDy; + private final float mDSize2; + + public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[], + float positions[], TileMode tile) { + super(colors, positions, tile); + mX0 = x0; + mY0 = y0; + mDx = x1 - x0; + mDy = y1 - y0; + mDSize2 = mDx * mDx + mDy * mDy; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + precomputeGradientColors(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in LinearGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class LinearGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + private LinearGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix. + pt1[0] = pt2[0]; + pt1[1] = pt2[1]; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + data[index++] = getColor(pt2[0], pt2[1]); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + } + + /** + * Returns a color for an arbitrary point. + */ + private int getColor(float x, float y) { + float pos; + if (mDx == 0) { + pos = (y - mY0) / mDy; + } else if (mDy == 0) { + pos = (x - mX0) / mDx; + } else { + // find the x position on the gradient vector. + float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2; + // from it get the position relative to the vector + pos = (_x - mX0) / mDx; + } + + return getGradientColor(pos); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java new file mode 100644 index 0000000..c2f27e4 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.MaskFilter + * + * Through the layoutlib_create tool, the original native methods of MaskFilter have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original MaskFilter class. + * + * This also serve as a base class for all MaskFilter delegate classes. + * + * @see DelegateManager + * + */ +public abstract class MaskFilter_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<MaskFilter_Delegate> sManager = + new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static MaskFilter_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_filter) { + sManager.removeJavaReferenceFor(native_filter); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java deleted file mode 100644 index 9e30671..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java +++ /dev/null @@ -1,1032 +0,0 @@ -/* - * Copyright (C) 2008 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.geom.AffineTransform; -import java.awt.geom.NoninvertibleTransformException; - - -/** - * A matrix implementation overridden by the LayoutLib bridge. - */ -public class Matrix extends _Original_Matrix { - - float mValues[] = new float[9]; - - /** - * Create an identity matrix - */ - public Matrix() { - reset(); - } - - /** - * Create a matrix that is a (deep) copy of src - * @param src The matrix to copy into this matrix - */ - public Matrix(Matrix src) { - set(src); - } - - /** - * Creates a Matrix object from the float array. The array becomes the internal storage - * of the object. - * @param data - */ - private Matrix(float[] data) { - assert data.length != 9; - mValues = data; - } - - //---------- Custom Methods - - /** - * Adds the given transformation to the current Matrix - * <p/>This in effect does this = this*matrix - * @param matrix - */ - private void addTransform(float[] matrix) { - float[] tmp = new float[9]; - - // first row - tmp[0] = matrix[0] * mValues[0] + matrix[1] * mValues[3] + matrix[2] * mValues[6]; - tmp[1] = matrix[0] * mValues[1] + matrix[1] * mValues[4] + matrix[2] * mValues[7]; - tmp[2] = matrix[0] * mValues[2] + matrix[1] * mValues[5] + matrix[2] * mValues[8]; - - // 2nd row - tmp[3] = matrix[3] * mValues[0] + matrix[4] * mValues[3] + matrix[5] * mValues[6]; - tmp[4] = matrix[3] * mValues[1] + matrix[4] * mValues[4] + matrix[5] * mValues[7]; - tmp[5] = matrix[3] * mValues[2] + matrix[4] * mValues[5] + matrix[5] * mValues[8]; - - // 3rd row - tmp[6] = matrix[6] * mValues[0] + matrix[7] * mValues[3] + matrix[8] * mValues[6]; - tmp[7] = matrix[6] * mValues[1] + matrix[7] * mValues[4] + matrix[8] * mValues[7]; - tmp[8] = matrix[6] * mValues[2] + matrix[7] * mValues[5] + matrix[8] * mValues[8]; - - // copy the result over to mValues - mValues = tmp; - } - - public AffineTransform getTransform() { - // the AffineTransform constructor takes the value in a different order - // for a matrix [ 0 1 2 ] - // [ 3 4 5 ] - // the order is 0, 3, 1, 4, 2, 5... - return new AffineTransform(mValues[0], mValues[3], mValues[1], - mValues[4], mValues[2], mValues[5]); - } - - public boolean hasPerspective() { - return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1); - } - - //---------- - - /** - * Returns true if the matrix is identity. - * This maybe faster than testing if (getType() == 0) - */ - @Override - public boolean isIdentity() { - for (int i = 0, k = 0; i < 3; i++) { - for (int j = 0; j < 3; j++, k++) { - if (mValues[k] != ((i==j) ? 1 : 0)) { - return false; - } - } - } - - return true; - } - - /** - * Returns true if will map a rectangle to another rectangle. This can be - * true if the matrix is identity, scale-only, or rotates a multiple of 90 - * degrees. - */ - @Override - public boolean rectStaysRect() { - return (computeTypeMask() & kRectStaysRect_Mask) != 0; - } - - /** - * (deep) copy the src matrix into this matrix. If src is null, reset this - * matrix to the identity matrix. - */ - public void set(Matrix src) { - if (src == null) { - reset(); - } else { - System.arraycopy(src.mValues, 0, mValues, 0, mValues.length); - } - } - - @Override - public void set(_Original_Matrix src) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** Returns true if obj is a Matrix and its values equal our values. - */ - @Override - public boolean equals(Object obj) { - if (obj != null && obj instanceof Matrix) { - Matrix matrix = (Matrix)obj; - for (int i = 0 ; i < 9 ; i++) { - if (mValues[i] != matrix.mValues[i]) { - return false; - } - } - - return true; - } - - return false; - } - - /** Set the matrix to identity */ - @Override - public void reset() { - for (int i = 0, k = 0; i < 3; i++) { - for (int j = 0; j < 3; j++, k++) { - mValues[k] = ((i==j) ? 1 : 0); - } - } - } - - /** Set the matrix to translate by (dx, dy). */ - @Override - public void setTranslate(float dx, float dy) { - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = dx; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = dy; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to scale by sx and sy, with a pivot point at (px, py). - * The pivot point is the coordinate that should remain unchanged by the - * specified transformation. - */ - @Override - public void setScale(float sx, float sy, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to scale by sx and sy. */ - @Override - public void setScale(float sx, float sy) { - mValues[0] = sx; - mValues[1] = 0; - mValues[2] = 0; - mValues[3] = 0; - mValues[4] = sy; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to rotate by the specified number of degrees, with a pivot - * point at (px, py). The pivot point is the coordinate that should remain - * unchanged by the specified transformation. - */ - @Override - public void setRotate(float degrees, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** - * Set the matrix to rotate about (0,0) by the specified number of degrees. - */ - @Override - public void setRotate(float degrees) { - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - - mValues[0] = cos; - mValues[1] = -sin; - mValues[2] = 0; - mValues[3] = sin; - mValues[4] = cos; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to rotate by the specified sine and cosine values, with a - * pivot point at (px, py). The pivot point is the coordinate that should - * remain unchanged by the specified transformation. - */ - @Override - public void setSinCos(float sinValue, float cosValue, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { cosValue, -sinValue, 0, sinValue, cosValue, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to rotate by the specified sine and cosine values. */ - @Override - public void setSinCos(float sinValue, float cosValue) { - mValues[0] = cosValue; - mValues[1] = -sinValue; - mValues[2] = 0; - mValues[3] = sinValue; - mValues[4] = cosValue; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to skew by sx and sy, with a pivot point at (px, py). - * The pivot point is the coordinate that should remain unchanged by the - * specified transformation. - */ - @Override - public void setSkew(float kx, float ky, float px, float py) { - // TODO: do it in one pass - - // translate so that the pivot is in 0,0 - mValues[0] = 1; - mValues[1] = 0; - mValues[2] = -px; - mValues[3] = 0; - mValues[4] = 1; - mValues[5] = -py; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - - // scale - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - } - - /** Set the matrix to skew by sx and sy. */ - @Override - public void setSkew(float kx, float ky) { - mValues[0] = 1; - mValues[1] = kx; - mValues[2] = -0; - mValues[3] = ky; - mValues[4] = 1; - mValues[5] = 0; - mValues[6] = 0; - mValues[7] = 0; - mValues[8] = 1; - } - - /** - * Set the matrix to the concatenation of the two specified matrices, - * returning true if the the result can be represented. Either of the two - * matrices may also be the target matrix. this = a * b - */ - public boolean setConcat(Matrix a, Matrix b) { - if (a == this) { - preConcat(b); - } else if (b == this) { - postConcat(b); - } else { - Matrix tmp = new Matrix(b); - tmp.addTransform(a.mValues); - set(tmp); - } - - return true; - } - - @Override - public boolean setConcat(_Original_Matrix a, _Original_Matrix b) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Preconcats the matrix with the specified translation. - * M' = M * T(dx, dy) - */ - @Override - public boolean preTranslate(float dx, float dy) { - // create a matrix that will be multiply by this - Matrix m = new Matrix(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 }); - m.addTransform(this.mValues); - - System.arraycopy(m.mValues, 0, mValues, 0, 9); - return true; - } - - /** - * Preconcats the matrix with the specified scale. - * M' = M * S(sx, sy, px, py) - */ - @Override - public boolean preScale(float sx, float sy, float px, float py) { - Matrix m = new Matrix(); - m.setScale(sx, sy, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified scale. - * M' = M * S(sx, sy) - */ - @Override - public boolean preScale(float sx, float sy) { - Matrix m = new Matrix(); - m.setScale(sx, sy); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified rotation. - * M' = M * R(degrees, px, py) - */ - @Override - public boolean preRotate(float degrees, float px, float py) { - Matrix m = new Matrix(); - m.setRotate(degrees, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified rotation. - * M' = M * R(degrees) - */ - @Override - public boolean preRotate(float degrees) { - Matrix m = new Matrix(); - m.setRotate(degrees); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified skew. - * M' = M * K(kx, ky, px, py) - */ - @Override - public boolean preSkew(float kx, float ky, float px, float py) { - Matrix m = new Matrix(); - m.setSkew(kx, ky, px, py); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified skew. - * M' = M * K(kx, ky) - */ - @Override - public boolean preSkew(float kx, float ky) { - Matrix m = new Matrix(); - m.setSkew(kx, ky); - m.addTransform(mValues); - set(m); - - return true; - } - - /** - * Preconcats the matrix with the specified matrix. - * M' = M * other - */ - public boolean preConcat(Matrix other) { - Matrix m = new Matrix(other); - other.addTransform(mValues); - set(m); - - return true; - } - - @Override - public boolean preConcat(_Original_Matrix other) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Postconcats the matrix with the specified translation. - * M' = T(dx, dy) * M - */ - @Override - public boolean postTranslate(float dx, float dy) { - addTransform(new float[] { 1, 0, dx, 0, 1, dy, 0, 0, 1 }); - return true; - } - - /** - * Postconcats the matrix with the specified scale. - * M' = S(sx, sy, px, py) * M - */ - @Override - public boolean postScale(float sx, float sy, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified scale. - * M' = S(sx, sy) * M - */ - @Override - public boolean postScale(float sx, float sy) { - addTransform(new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }); - return true; - } - - /** - * Postconcats the matrix with the specified rotation. - * M' = R(degrees, px, py) * M - */ - @Override - public boolean postRotate(float degrees, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified rotation. - * M' = R(degrees) * M - */ - @Override - public boolean postRotate(float degrees) { - double rad = Math.toRadians(degrees); - float cos = (float)Math.cos(rad); - float sin = (float)Math.sin(rad); - addTransform(new float[] { cos, -sin, 0, sin, cos, 0, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified skew. - * M' = K(kx, ky, px, py) * M - */ - @Override - public boolean postSkew(float kx, float ky, float px, float py) { - // TODO: do it in one pass - // translate so that the pivot is in 0,0 - addTransform(new float[] { 1, 0, -px, 0, 1, py, 0, 0, 1 }); - // scale - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - // translate back the pivot - addTransform(new float[] { 1, 0, px, 0, 1, py, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified skew. - * M' = K(kx, ky) * M - */ - @Override - public boolean postSkew(float kx, float ky) { - addTransform(new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); - - return true; - } - - /** - * Postconcats the matrix with the specified matrix. - * M' = other * M - */ - public boolean postConcat(Matrix other) { - addTransform(other.mValues); - - return true; - } - - @Override - public boolean postConcat(_Original_Matrix other) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** Controlls how the src rect should align into the dst rect for - setRectToRect(). - */ - public enum ScaleToFit { - /** - * Scale in X and Y independently, so that src matches dst exactly. - * This may change the aspect ratio of the src. - */ - FILL (0), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. START aligns the result to the - * left and top edges of dst. - */ - START (1), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. The result is centered inside dst. - */ - CENTER (2), - /** - * Compute a scale that will maintain the original src aspect ratio, - * but will also ensure that src fits entirely inside dst. At least one - * axis (X or Y) will fit exactly. END aligns the result to the - * right and bottom edges of dst. - */ - END (3); - - // the native values must match those in SkMatrix.h - ScaleToFit(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * Set the matrix to the scale and translate values that map the source - * rectangle to the destination rectangle, returning true if the result - * can be represented. - * - * @param src the source rectangle to map from. - * @param dst the destination rectangle to map to. - * @param stf the ScaleToFit option - * @return true if the matrix can be represented by the rectangle mapping. - */ - public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) { - if (dst == null || src == null) { - throw new NullPointerException(); - } - - if (src.isEmpty()) { - reset(); - return false; - } - - if (dst.isEmpty()) { - mValues[0] = mValues[1] = mValues[2] = mValues[3] = mValues[4] = mValues[5] - = mValues[6] = mValues[7] = 0; - mValues[8] = 1; - } else { - float tx, sx = dst.width() / src.width(); - float ty, sy = dst.height() / src.height(); - boolean xLarger = false; - - if (stf != ScaleToFit.FILL) { - if (sx > sy) { - xLarger = true; - sx = sy; - } else { - sy = sx; - } - } - - tx = dst.left - src.left * sx; - ty = dst.top - src.top * sy; - if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) { - float diff; - - if (xLarger) { - diff = dst.width() - src.width() * sy; - } else { - diff = dst.height() - src.height() * sy; - } - - if (stf == ScaleToFit.CENTER) { - diff = diff / 2; - } - - if (xLarger) { - tx += diff; - } else { - ty += diff; - } - } - - mValues[0] = sx; - mValues[4] = sy; - mValues[2] = tx; - mValues[5] = ty; - mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0; - - } - // shared cleanup - mValues[8] = 1; - return true; - } - - @Override - public boolean setRectToRect(RectF src, RectF dst, _Original_Matrix.ScaleToFit stf) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Set the matrix such that the specified src points would map to the - * specified dst points. The "points" are represented as an array of floats, - * order [x0, y0, x1, y1, ...], where each "point" is 2 float values. - * - * @param src The array of src [x,y] pairs (points) - * @param srcIndex Index of the first pair of src values - * @param dst The array of dst [x,y] pairs (points) - * @param dstIndex Index of the first pair of dst values - * @param pointCount The number of pairs/points to be used. Must be [0..4] - * @return true if the matrix was set to the specified transformation - */ - @Override - public boolean setPolyToPoly(float[] src, int srcIndex, - float[] dst, int dstIndex, - int pointCount) { - if (pointCount > 4) { - throw new IllegalArgumentException(); - } - checkPointArrays(src, srcIndex, dst, dstIndex, pointCount); - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** - * If this matrix can be inverted, return true and if inverse is not null, - * set inverse to be the inverse of this matrix. If this matrix cannot be - * inverted, ignore inverse and return false. - */ - public boolean invert(Matrix inverse) { - if (inverse == null) { - return false; - } - - try { - AffineTransform affineTransform = getTransform(); - AffineTransform inverseTransform = affineTransform.createInverse(); - inverse.mValues[0] = (float)inverseTransform.getScaleX(); - inverse.mValues[1] = (float)inverseTransform.getShearX(); - inverse.mValues[2] = (float)inverseTransform.getTranslateX(); - inverse.mValues[3] = (float)inverseTransform.getScaleX(); - inverse.mValues[4] = (float)inverseTransform.getShearY(); - inverse.mValues[5] = (float)inverseTransform.getTranslateY(); - - return true; - } catch (NoninvertibleTransformException e) { - return false; - } - } - - @Override - public boolean invert(_Original_Matrix inverse) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - /** - * Apply this matrix to the array of 2D points specified by src, and write - * the transformed points into the array of points specified by dst. The - * two arrays represent their "points" as pairs of floats [x, y]. - * - * @param dst The array of dst points (x,y pairs) - * @param dstIndex The index of the first [x,y] pair of dst floats - * @param src The array of src points (x,y pairs) - * @param srcIndex The index of the first [x,y] pair of src floats - * @param pointCount The number of points (x,y pairs) to transform - */ - @Override - public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, - int pointCount) { - checkPointArrays(src, srcIndex, dst, dstIndex, pointCount); - - for (int i = 0 ; i < pointCount ; i++) { - // just in case we are doing in place, we better put this in temp vars - float x = mValues[0] * src[i + srcIndex] + - mValues[1] * src[i + srcIndex + 1] + - mValues[2]; - float y = mValues[3] * src[i + srcIndex] + - mValues[4] * src[i + srcIndex + 1] + - mValues[5]; - - dst[i + dstIndex] = x; - dst[i + dstIndex + 1] = y; - } - } - - /** - * Apply this matrix to the array of 2D vectors specified by src, and write - * the transformed vectors into the array of vectors specified by dst. The - * two arrays represent their "vectors" as pairs of floats [x, y]. - * - * @param dst The array of dst vectors (x,y pairs) - * @param dstIndex The index of the first [x,y] pair of dst floats - * @param src The array of src vectors (x,y pairs) - * @param srcIndex The index of the first [x,y] pair of src floats - * @param vectorCount The number of vectors (x,y pairs) to transform - */ - @Override - public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, - int vectorCount) { - checkPointArrays(src, srcIndex, dst, dstIndex, vectorCount); - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** - * Apply this matrix to the array of 2D points specified by src, and write - * the transformed points into the array of points specified by dst. The - * two arrays represent their "points" as pairs of floats [x, y]. - * - * @param dst The array of dst points (x,y pairs) - * @param src The array of src points (x,y pairs) - */ - @Override - public void mapPoints(float[] dst, float[] src) { - if (dst.length != src.length) { - throw new ArrayIndexOutOfBoundsException(); - } - mapPoints(dst, 0, src, 0, dst.length >> 1); - } - - /** - * Apply this matrix to the array of 2D vectors specified by src, and write - * the transformed vectors into the array of vectors specified by dst. The - * two arrays represent their "vectors" as pairs of floats [x, y]. - * - * @param dst The array of dst vectors (x,y pairs) - * @param src The array of src vectors (x,y pairs) - */ - @Override - public void mapVectors(float[] dst, float[] src) { - if (dst.length != src.length) { - throw new ArrayIndexOutOfBoundsException(); - } - mapVectors(dst, 0, src, 0, dst.length >> 1); - } - - /** - * Apply this matrix to the array of 2D points, and write the transformed - * points back into the array - * - * @param pts The array [x0, y0, x1, y1, ...] of points to transform. - */ - @Override - public void mapPoints(float[] pts) { - mapPoints(pts, 0, pts, 0, pts.length >> 1); - } - - /** - * Apply this matrix to the array of 2D vectors, and write the transformed - * vectors back into the array. - * @param vecs The array [x0, y0, x1, y1, ...] of vectors to transform. - */ - @Override - public void mapVectors(float[] vecs) { - mapVectors(vecs, 0, vecs, 0, vecs.length >> 1); - } - - /** - * Apply this matrix to the src rectangle, and write the transformed - * rectangle into dst. This is accomplished by transforming the 4 corners of - * src, and then setting dst to the bounds of those points. - * - * @param dst Where the transformed rectangle is written. - * @param src The original rectangle to be transformed. - * @return the result of calling rectStaysRect() - */ - @Override - public boolean mapRect(RectF dst, RectF src) { - if (dst == null || src == null) { - throw new NullPointerException(); - } - - // array with 4 corners - float[] corners = new float[] { - src.left, src.top, - src.right, src.top, - src.right, src.bottom, - src.left, src.bottom, - }; - - // apply the transform to them. - mapPoints(corners); - - // now put the result in the rect. We take the min/max of Xs and min/max of Ys - dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); - dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); - - dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); - dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); - - return rectStaysRect(); - } - - /** - * Apply this matrix to the rectangle, and write the transformed rectangle - * back into it. This is accomplished by transforming the 4 corners of rect, - * and then setting it to the bounds of those points - * - * @param rect The rectangle to transform. - * @return the result of calling rectStaysRect() - */ - @Override - public boolean mapRect(RectF rect) { - return mapRect(rect, rect); - } - - /** - * Return the mean radius of a circle after it has been mapped by - * this matrix. NOTE: in perspective this value assumes the circle - * has its center at the origin. - */ - @Override - public float mapRadius(float radius) { - throw new UnsupportedOperationException("STUB NEEDED"); - } - - /** Copy 9 values from the matrix into the array. - */ - @Override - public void getValues(float[] values) { - if (values.length < 9) { - throw new ArrayIndexOutOfBoundsException(); - } - System.arraycopy(mValues, 0, values, 0, mValues.length); - } - - /** Copy 9 values from the array into the matrix. - Depending on the implementation of Matrix, these may be - transformed into 16.16 integers in the Matrix, such that - a subsequent call to getValues() will not yield exactly - the same values. - */ - @Override - public void setValues(float[] values) { - if (values.length < 9) { - throw new ArrayIndexOutOfBoundsException(); - } - System.arraycopy(values, 0, mValues, 0, mValues.length); - } - - @SuppressWarnings("unused") - private final static int kIdentity_Mask = 0; - private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation - private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale - private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates - private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective - private final static int kRectStaysRect_Mask = 0x10; - @SuppressWarnings("unused") - private final static int kUnknown_Mask = 0x80; - - @SuppressWarnings("unused") - private final static int kAllMasks = kTranslate_Mask | - kScale_Mask | - kAffine_Mask | - kPerspective_Mask | - kRectStaysRect_Mask; - - // these guys align with the masks, so we can compute a mask from a variable 0/1 - @SuppressWarnings("unused") - private final static int kTranslate_Shift = 0; - @SuppressWarnings("unused") - private final static int kScale_Shift = 1; - @SuppressWarnings("unused") - private final static int kAffine_Shift = 2; - @SuppressWarnings("unused") - private final static int kPerspective_Shift = 3; - private final static int kRectStaysRect_Shift = 4; - - private int computeTypeMask() { - int mask = 0; - - if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) { - mask |= kPerspective_Mask; - } - - if (mValues[2] != 0. || mValues[5] != 0.) { - mask |= kTranslate_Mask; - } - - float m00 = mValues[0]; - float m01 = mValues[1]; - float m10 = mValues[3]; - float m11 = mValues[4]; - - if (m01 != 0. || m10 != 0.) { - mask |= kAffine_Mask; - } - - if (m00 != 1. || m11 != 1.) { - mask |= kScale_Mask; - } - - if ((mask & kPerspective_Mask) == 0) { - // map non-zero to 1 - int im00 = m00 != 0 ? 1 : 0; - int im01 = m01 != 0 ? 1 : 0; - int im10 = m10 != 0 ? 1 : 0; - int im11 = m11 != 0 ? 1 : 0; - - // record if the (p)rimary and (s)econdary diagonals are all 0 or - // all non-zero (answer is 0 or 1) - int dp0 = (im00 | im11) ^ 1; // true if both are 0 - int dp1 = im00 & im11; // true if both are 1 - int ds0 = (im01 | im10) ^ 1; // true if both are 0 - int ds1 = im01 & im10; // true if both are 1 - - // return 1 if primary is 1 and secondary is 0 or - // primary is 0 and secondary is 1 - mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift; - } - - return mask; - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java new file mode 100644 index 0000000..451edd2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -0,0 +1,1129 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Matrix.ScaleToFit; + +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; + +/** + * Delegate implementing the native methods of android.graphics.Matrix + * + * Through the layoutlib_create tool, the original native methods of Matrix have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Matrix class. + * + * @see DelegateManager + * + */ +public final class Matrix_Delegate { + + private final static int MATRIX_SIZE = 9; + + // ---- delegate manager ---- + private static final DelegateManager<Matrix_Delegate> sManager = + new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class); + + // ---- delegate data ---- + private float mValues[] = new float[MATRIX_SIZE]; + + // ---- Public Helper methods ---- + + public static Matrix_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + /** + * Returns an {@link AffineTransform} matching the given Matrix. + */ + public static AffineTransform getAffineTransform(Matrix m) { + Matrix_Delegate delegate = sManager.getDelegate(m.native_instance); + if (delegate == null) { + return null; + } + + return delegate.getAffineTransform(); + } + + public static boolean hasPerspective(Matrix m) { + Matrix_Delegate delegate = sManager.getDelegate(m.native_instance); + if (delegate == null) { + return false; + } + + return delegate.hasPerspective(); + } + + /** + * Sets the content of the matrix with the content of another matrix. + */ + public void set(Matrix_Delegate matrix) { + System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Sets the content of the matrix with the content of another matrix represented as an array + * of values. + */ + public void set(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Resets the matrix to be the identity matrix. + */ + public void reset() { + reset(mValues); + } + + /** + * Returns whether or not the matrix is identity. + */ + public boolean isIdentity() { + for (int i = 0, k = 0; i < 3; i++) { + for (int j = 0; j < 3; j++, k++) { + if (mValues[k] != ((i==j) ? 1 : 0)) { + return false; + } + } + } + + return true; + } + + public static float[] makeValues(AffineTransform matrix) { + float[] values = new float[MATRIX_SIZE]; + values[0] = (float) matrix.getScaleX(); + values[1] = (float) matrix.getShearX(); + values[2] = (float) matrix.getTranslateX(); + values[3] = (float) matrix.getShearY(); + values[4] = (float) matrix.getScaleY(); + values[5] = (float) matrix.getTranslateY(); + values[6] = 0.f; + values[7] = 0.f; + values[8] = 1.f; + + return values; + } + + public static Matrix_Delegate make(AffineTransform matrix) { + return new Matrix_Delegate(makeValues(matrix)); + } + + public boolean mapRect(RectF dst, RectF src) { + // array with 4 corners + float[] corners = new float[] { + src.left, src.top, + src.right, src.top, + src.right, src.bottom, + src.left, src.bottom, + }; + + // apply the transform to them. + mapPoints(corners); + + // now put the result in the rect. We take the min/max of Xs and min/max of Ys + dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); + dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); + + dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); + dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); + + + return (computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + + /** + * Returns an {@link AffineTransform} matching the matrix. + */ + public AffineTransform getAffineTransform() { + return getAffineTransform(mValues); + } + + public boolean hasPerspective() { + return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1); + } + + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_create(int native_src_or_zero) { + // create the delegate + Matrix_Delegate newDelegate = new Matrix_Delegate(); + + // copy from values if needed. + if (native_src_or_zero > 0) { + Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero); + if (oldDelegate != null) { + System.arraycopy( + oldDelegate.mValues, 0, + newDelegate.mValues, 0, + MATRIX_SIZE); + } + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isIdentity(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.isIdentity(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_rectStaysRect(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kRectStaysRect_Mask) != 0; + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + reset(d.mValues); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_object, int other) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + Matrix_Delegate src = sManager.getDelegate(other); + if (src == null) { + return; + } + + System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setTranslate(d.mValues, dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_setScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getScale(sx, sy, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues[0] = sx; + d.mValues[1] = 0; + d.mValues[2] = 0; + d.mValues[3] = 0; + d.mValues[4] = sy; + d.mValues[5] = 0; + d.mValues[6] = 0; + d.mValues[7] = 0; + d.mValues[8] = 1; + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees, float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getRotate(degrees, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, degrees); + } + + @LayoutlibDelegate + /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(d.mValues, -px, -py); + + // scale + d.postTransform(getRotate(sinValue, cosValue)); + // translate back the pivot + d.postTransform(getTranslate(px, py)); + } + + @LayoutlibDelegate + /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + setRotate(d.mValues, sinValue, cosValue); + } + + @LayoutlibDelegate + /*package*/ static void native_setSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues = getSkew(kx, ky, px, py); + } + + @LayoutlibDelegate + /*package*/ static void native_setSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + d.mValues[0] = 1; + d.mValues[1] = kx; + d.mValues[2] = -0; + d.mValues[3] = ky; + d.mValues[4] = 1; + d.mValues[5] = 0; + d.mValues[6] = 0; + d.mValues[7] = 0; + d.mValues[8] = 1; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setConcat(int native_object, int a, int b) { + if (a == native_object) { + return native_preConcat(native_object, b); + } else if (b == native_object) { + return native_postConcat(native_object, a); + } + + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate a_mtx = sManager.getDelegate(a); + if (a_mtx == null) { + return false; + } + + Matrix_Delegate b_mtx = sManager.getDelegate(b); + if (b_mtx == null) { + return false; + } + + multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + d.preTransform(getRotate(sin, cos)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.preTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_preConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.preTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postTranslate(int native_object, float dx, float dy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getTranslate(dx, dy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postScale(int native_object, float sx, float sy, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getScale(sx, sy, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postScale(int native_object, float sx, float sy) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getScale(sx, sy)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postRotate(int native_object, float degrees) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getRotate(degrees)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postSkew(int native_object, float kx, float ky, + float px, float py) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getSkew(kx, ky, px, py)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postSkew(int native_object, float kx, float ky) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + d.postTransform(getSkew(kx, ky)); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_postConcat(int native_object, int other_matrix) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate other = sManager.getDelegate(other_matrix); + if (d == null) { + return false; + } + + d.postTransform(other.mValues); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setRectToRect(int native_object, RectF src, + RectF dst, int stf) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + if (src.isEmpty()) { + reset(d.mValues); + return false; + } + + if (dst.isEmpty()) { + d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5] + = d.mValues[6] = d.mValues[7] = 0; + d.mValues[8] = 1; + } else { + float tx, sx = dst.width() / src.width(); + float ty, sy = dst.height() / src.height(); + boolean xLarger = false; + + if (stf != ScaleToFit.FILL.nativeInt) { + if (sx > sy) { + xLarger = true; + sx = sy; + } else { + sy = sx; + } + } + + tx = dst.left - src.left * sx; + ty = dst.top - src.top * sy; + if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) { + float diff; + + if (xLarger) { + diff = dst.width() - src.width() * sy; + } else { + diff = dst.height() - src.height() * sy; + } + + if (stf == ScaleToFit.CENTER.nativeInt) { + diff = diff / 2; + } + + if (xLarger) { + tx += diff; + } else { + ty += diff; + } + } + + d.mValues[0] = sx; + d.mValues[4] = sy; + d.mValues[2] = tx; + d.mValues[5] = ty; + d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0; + + } + // shared cleanup + d.mValues[8] = 1; + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean native_setPolyToPoly(int native_object, float[] src, int srcIndex, + float[] dst, int dstIndex, int pointCount) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Matrix.setPolyToPoly is not supported.", + null, null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_invert(int native_object, int inverse) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + Matrix_Delegate inv_mtx = sManager.getDelegate(inverse); + if (inv_mtx == null) { + return false; + } + + try { + AffineTransform affineTransform = d.getAffineTransform(); + AffineTransform inverseTransform = affineTransform.createInverse(); + inv_mtx.mValues[0] = (float)inverseTransform.getScaleX(); + inv_mtx.mValues[1] = (float)inverseTransform.getShearX(); + inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX(); + inv_mtx.mValues[3] = (float)inverseTransform.getScaleX(); + inv_mtx.mValues[4] = (float)inverseTransform.getShearY(); + inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY(); + + return true; + } catch (NoninvertibleTransformException e) { + return false; + } + } + + @LayoutlibDelegate + /*package*/ static void native_mapPoints(int native_object, float[] dst, int dstIndex, + float[] src, int srcIndex, int ptCount, boolean isPts) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + if (isPts) { + d.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } else { + d.mapVectors(dst, dstIndex, src, srcIndex, ptCount); + } + } + + @LayoutlibDelegate + /*package*/ static boolean native_mapRect(int native_object, RectF dst, RectF src) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return false; + } + + return d.mapRect(dst, src); + } + + @LayoutlibDelegate + /*package*/ static float native_mapRadius(int native_object, float radius) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return 0.f; + } + + float[] src = new float[] { radius, 0.f, 0.f, radius }; + d.mapVectors(src, 0, src, 0, 2); + + float l1 = getPointLength(src, 0); + float l2 = getPointLength(src, 2); + + return (float) Math.sqrt(l1 * l2); + } + + @LayoutlibDelegate + /*package*/ static void native_getValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(d.mValues, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static void native_setValues(int native_object, float[] values) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return; + } + + System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE); + } + + @LayoutlibDelegate + /*package*/ static boolean native_equals(int native_a, int native_b) { + Matrix_Delegate a = sManager.getDelegate(native_a); + if (a == null) { + return false; + } + + Matrix_Delegate b = sManager.getDelegate(native_b); + if (b == null) { + return false; + } + + for (int i = 0 ; i < MATRIX_SIZE ; i++) { + if (a.mValues[i] != b.mValues[i]) { + return false; + } + } + + return true; + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private helper methods ---- + + /*package*/ static AffineTransform getAffineTransform(float[] matrix) { + // the AffineTransform constructor takes the value in a different order + // for a matrix [ 0 1 2 ] + // [ 3 4 5 ] + // the order is 0, 3, 1, 4, 2, 5... + return new AffineTransform( + matrix[0], matrix[3], matrix[1], + matrix[4], matrix[2], matrix[5]); + } + + /** + * Reset a matrix to the identity + */ + private static void reset(float[] mtx) { + for (int i = 0, k = 0; i < 3; i++) { + for (int j = 0; j < 3; j++, k++) { + mtx[k] = ((i==j) ? 1 : 0); + } + } + } + + @SuppressWarnings("unused") + private final static int kIdentity_Mask = 0; + private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation + private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale + private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates + private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective + private final static int kRectStaysRect_Mask = 0x10; + @SuppressWarnings("unused") + private final static int kUnknown_Mask = 0x80; + + @SuppressWarnings("unused") + private final static int kAllMasks = kTranslate_Mask | + kScale_Mask | + kAffine_Mask | + kPerspective_Mask | + kRectStaysRect_Mask; + + // these guys align with the masks, so we can compute a mask from a variable 0/1 + @SuppressWarnings("unused") + private final static int kTranslate_Shift = 0; + @SuppressWarnings("unused") + private final static int kScale_Shift = 1; + @SuppressWarnings("unused") + private final static int kAffine_Shift = 2; + @SuppressWarnings("unused") + private final static int kPerspective_Shift = 3; + private final static int kRectStaysRect_Shift = 4; + + private int computeTypeMask() { + int mask = 0; + + if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) { + mask |= kPerspective_Mask; + } + + if (mValues[2] != 0. || mValues[5] != 0.) { + mask |= kTranslate_Mask; + } + + float m00 = mValues[0]; + float m01 = mValues[1]; + float m10 = mValues[3]; + float m11 = mValues[4]; + + if (m01 != 0. || m10 != 0.) { + mask |= kAffine_Mask; + } + + if (m00 != 1. || m11 != 1.) { + mask |= kScale_Mask; + } + + if ((mask & kPerspective_Mask) == 0) { + // map non-zero to 1 + int im00 = m00 != 0 ? 1 : 0; + int im01 = m01 != 0 ? 1 : 0; + int im10 = m10 != 0 ? 1 : 0; + int im11 = m11 != 0 ? 1 : 0; + + // record if the (p)rimary and (s)econdary diagonals are all 0 or + // all non-zero (answer is 0 or 1) + int dp0 = (im00 | im11) ^ 1; // true if both are 0 + int dp1 = im00 & im11; // true if both are 1 + int ds0 = (im01 | im10) ^ 1; // true if both are 0 + int ds1 = im01 & im10; // true if both are 1 + + // return 1 if primary is 1 and secondary is 0 or + // primary is 0 and secondary is 1 + mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift; + } + + return mask; + } + + private Matrix_Delegate() { + reset(); + } + + private Matrix_Delegate(float[] values) { + System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE); + } + + /** + * Adds the given transformation to the current Matrix + * <p/>This in effect does this = this*matrix + * @param matrix + */ + private void postTransform(float[] matrix) { + float[] tmp = new float[9]; + multiply(tmp, mValues, matrix); + mValues = tmp; + } + + /** + * Adds the given transformation to the current Matrix + * <p/>This in effect does this = matrix*this + * @param matrix + */ + private void preTransform(float[] matrix) { + float[] tmp = new float[9]; + multiply(tmp, matrix, mValues); + mValues = tmp; + } + + /** + * Apply this matrix to the array of 2D points specified by src, and write + * the transformed points into the array of points specified by dst. The + * two arrays represent their "points" as pairs of floats [x, y]. + * + * @param dst The array of dst points (x,y pairs) + * @param dstIndex The index of the first [x,y] pair of dst floats + * @param src The array of src points (x,y pairs) + * @param srcIndex The index of the first [x,y] pair of src floats + * @param pointCount The number of points (x,y pairs) to transform + */ + + private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, + int pointCount) { + final int count = pointCount * 2; + + float[] tmpDest = dst; + boolean inPlace = dst == src; + if (inPlace) { + tmpDest = new float[dstIndex + count]; + } + + for (int i = 0 ; i < count ; i += 2) { + // just in case we are doing in place, we better put this in temp vars + float x = mValues[0] * src[i + srcIndex] + + mValues[1] * src[i + srcIndex + 1] + + mValues[2]; + float y = mValues[3] * src[i + srcIndex] + + mValues[4] * src[i + srcIndex + 1] + + mValues[5]; + + tmpDest[i + dstIndex] = x; + tmpDest[i + dstIndex + 1] = y; + } + + if (inPlace) { + System.arraycopy(tmpDest, dstIndex, dst, dstIndex, count); + } + } + + /** + * Apply this matrix to the array of 2D points, and write the transformed + * points back into the array + * + * @param pts The array [x0, y0, x1, y1, ...] of points to transform. + */ + + private void mapPoints(float[] pts) { + mapPoints(pts, 0, pts, 0, pts.length >> 1); + } + + private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) { + if (hasPerspective()) { + // transform the (0,0) point + float[] origin = new float[] { 0.f, 0.f}; + mapPoints(origin); + + // translate the vector data as points + mapPoints(dst, dstIndex, src, srcIndex, ptCount); + + // then substract the transformed origin. + final int count = ptCount * 2; + for (int i = 0 ; i < count ; i += 2) { + dst[dstIndex + i] = dst[dstIndex + i] - origin[0]; + dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1]; + } + } else { + // make a copy of the matrix + Matrix_Delegate copy = new Matrix_Delegate(mValues); + + // remove the translation + setTranslate(copy.mValues, 0, 0); + + // map the content as points. + copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount); + } + } + + private static float getPointLength(float[] src, int index) { + return (float) Math.sqrt(src[index] * src[index] + src[index + 1] * src[index + 1]); + } + + /** + * multiply two matrices and store them in a 3rd. + * <p/>This in effect does dest = a*b + * dest cannot be the same as a or b. + */ + /*package*/ static void multiply(float dest[], float[] a, float[] b) { + // first row + dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6]; + dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7]; + dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8]; + + // 2nd row + dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6]; + dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7]; + dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8]; + + // 3rd row + dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6]; + dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7]; + dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8]; + } + + /** + * Returns a matrix that represents a given translate + * @param dx + * @param dy + * @return + */ + /*package*/ static float[] getTranslate(float dx, float dy) { + return setTranslate(new float[9], dx, dy); + } + + /*package*/ static float[] setTranslate(float[] dest, float dx, float dy) { + dest[0] = 1; + dest[1] = 0; + dest[2] = dx; + dest[3] = 0; + dest[4] = 1; + dest[5] = dy; + dest[6] = 0; + dest[7] = 0; + dest[8] = 1; + return dest; + } + + /*package*/ static float[] getScale(float sx, float sy) { + return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; + } + + /** + * Returns a matrix that represents the given scale info. + * @param sx + * @param sy + * @param px + * @param py + */ + /*package*/ static float[] getScale(float sx, float sy, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate tmp so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // scale into tmp2 + multiply(tmp2, tmp, getScale(sx, sy)); + + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } + + + /*package*/ static float[] getRotate(float degrees) { + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + return getRotate(sin, cos); + } + + /*package*/ static float[] getRotate(float sin, float cos) { + return setRotate(new float[9], sin, cos); + } + + /*package*/ static float[] setRotate(float[] dest, float degrees) { + double rad = Math.toRadians(degrees); + float sin = (float)Math.sin(rad); + float cos = (float)Math.cos(rad); + + return setRotate(dest, sin, cos); + } + + /*package*/ static float[] setRotate(float[] dest, float sin, float cos) { + dest[0] = cos; + dest[1] = -sin; + dest[2] = 0; + dest[3] = sin; + dest[4] = cos; + dest[5] = 0; + dest[6] = 0; + dest[7] = 0; + dest[8] = 1; + return dest; + } + + /*package*/ static float[] getRotate(float degrees, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // rotate into tmp2 + double rad = Math.toRadians(degrees); + float cos = (float)Math.cos(rad); + float sin = (float)Math.sin(rad); + multiply(tmp2, tmp, getRotate(sin, cos)); + + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } + + /*package*/ static float[] getSkew(float kx, float ky) { + return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }; + } + + /*package*/ static float[] getSkew(float kx, float ky, float px, float py) { + float[] tmp = new float[9]; + float[] tmp2 = new float[9]; + + // TODO: do it in one pass + + // translate so that the pivot is in 0,0 + setTranslate(tmp, -px, -py); + + // skew into tmp2 + multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 }); + // translate back the pivot back into tmp + multiply(tmp, tmp2, getTranslate(px, py)); + + return tmp; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java new file mode 100644 index 0000000..5e882ce --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.ninepatch.NinePatchChunk; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.drawable.NinePatchDrawable; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; + +/** + * Delegate implementing the native methods of android.graphics.NinePatch + * + * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public final class NinePatch_Delegate { + + /** + * Cache map for {@link NinePatchChunk}. + * When the chunks are created they are serialized into a byte[], and both are put + * in the cache, using a {@link SoftReference} for the chunk. The default Java classes + * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and + * provide this for drawing. + * Using the cache map allows us to not have to deserialize the byte[] back into a + * {@link NinePatchChunk} every time a rendering is done. + */ + private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = + new HashMap<byte[], SoftReference<NinePatchChunk>>(); + + // ---- Public Helper methods ---- + + /** + * Serializes the given chunk. + * + * @return the serialized data for the chunk. + */ + public static byte[] serialize(NinePatchChunk chunk) { + // serialize the chunk to get a byte[] + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos); + oos.writeObject(chunk); + } catch (IOException e) { + Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/); + return null; + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + // get the array and add it to the cache + byte[] array = baos.toByteArray(); + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + return array; + } + + /** + * Returns a {@link NinePatchChunk} object for the given serialized representation. + * + * If the chunk is present in the cache then the object from the cache is returned, otherwise + * the array is deserialized into a {@link NinePatchChunk} object. + * + * @param array the serialized representation of the chunk. + * @return the NinePatchChunk or null if deserialization failed. + */ + public static NinePatchChunk getChunk(byte[] array) { + SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array); + NinePatchChunk chunk = chunkRef.get(); + if (chunk == null) { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(bais); + chunk = (NinePatchChunk) ois.readObject(); + + // put back the chunk in the cache + if (chunk != null) { + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + } + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk content.", e, null /*data*/); + return null; + } catch (ClassNotFoundException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to deserialize NinePatchChunk class.", e, null /*data*/); + return null; + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + } + } + } + } + + return chunk; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isNinePatchChunk(byte[] chunk) { + NinePatchChunk chunkObject = getChunk(chunk); + if (chunkObject != null) { + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) { + // the default JNI implementation only checks that the byte[] has the same + // size as the C struct it represent. Since we cannot do the same check (serialization + // will return different size depending on content), we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + loc.left, loc.top, loc.width(), loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) { + return 0; + } + + // ---- Private Helper methods ---- + + private static void draw(int canvas_instance, + final int left, final int top, final int right, final int bottom, + int bitmap_instance, byte[] c, int paint_instance_or_null, + final int destDensity, final int srcDensity) { + // get the delegate from the native int. + final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance); + if (bitmap_delegate == null) { + return; + } + + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmap_delegate.getImage(); + Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, + new Rect(0, 0, image.getWidth(), image.getHeight()), + new Rect(left, top, right, bottom), + paint_instance_or_null, destDensity, srcDensity); + return; + } + + final NinePatchChunk chunkObject = getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance); + if (canvas_delegate == null) { + return; + } + + // this one can be null + Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); + + canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() { + public void draw(Graphics2D graphics, Paint_Delegate paint) { + chunkObject.draw(bitmap_delegate.getImage(), graphics, + left, top, right - left, bottom - top, destDensity, srcDensity); + } + }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/); + + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java deleted file mode 100644 index d13b5fe..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ /dev/null @@ -1,1211 +0,0 @@ -/* - * Copyright (C) 2008 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 android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.SpannedString; -import android.text.TextUtils; - -import java.awt.BasicStroke; -import java.awt.Font; -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; - -/** - * A paint implementation overridden by the LayoutLib bridge. - */ -public class Paint extends _Original_Paint { - - private int mColor = 0xFFFFFFFF; - private float mStrokeWidth = 1.f; - private float mTextSize = 20; - private float mScaleX = 1; - private float mSkewX = 0; - private Align mAlign = Align.LEFT; - private Style mStyle = Style.FILL; - private float mStrokeMiter = 4.0f; - private Cap mCap = Cap.BUTT; - private Join mJoin = Join.MITER; - private int mFlags = 0; - - /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. - */ - public static final class FontInfo { - Font mFont; - java.awt.FontMetrics mMetrics; - } - - private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); - - public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; - public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG; - public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG; - public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG; - public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG; - public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG; - public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG; - public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG; - public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG; - - public static class FontMetrics extends _Original_Paint.FontMetrics { - } - - public static class FontMetricsInt extends _Original_Paint.FontMetricsInt { - } - - /** - * The Style specifies if the primitive being drawn is filled, - * stroked, or both (in the same color). The default is FILL. - */ - public enum Style { - /** - * Geometry and text drawn with this style will be filled, ignoring all - * stroke-related settings in the paint. - */ - FILL (0), - /** - * Geometry and text drawn with this style will be stroked, respecting - * the stroke-related fields on the paint. - */ - STROKE (1), - /** - * Geometry and text drawn with this style will be both filled and - * stroked at the same time, respecting the stroke-related fields on - * the paint. - */ - FILL_AND_STROKE (2); - - Style(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * The Cap specifies the treatment for the beginning and ending of - * stroked lines and paths. The default is BUTT. - */ - public enum Cap { - /** - * The stroke ends with the path, and does not project beyond it. - */ - BUTT (0), - /** - * The stroke projects out as a square, with the center at the end - * of the path. - */ - ROUND (1), - /** - * The stroke projects out as a semicircle, with the center at the - * end of the path. - */ - SQUARE (2); - - private Cap(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaCap() { - switch (this) { - case BUTT: - return BasicStroke.CAP_BUTT; - case ROUND: - return BasicStroke.CAP_ROUND; - default: - case SQUARE: - return BasicStroke.CAP_SQUARE; - } - } - } - - /** - * The Join specifies the treatment where lines and curve segments - * join on a stroked path. The default is MITER. - */ - public enum Join { - /** - * The outer edges of a join meet at a sharp angle - */ - MITER (0), - /** - * The outer edges of a join meet in a circular arc. - */ - ROUND (1), - /** - * The outer edges of a join meet with a straight line - */ - BEVEL (2); - - private Join(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - - /** custom for layoutlib */ - public int getJavaJoin() { - switch (this) { - default: - case MITER: - return BasicStroke.JOIN_MITER; - case ROUND: - return BasicStroke.JOIN_ROUND; - case BEVEL: - return BasicStroke.JOIN_BEVEL; - } - } - } - - /** - * Align specifies how drawText aligns its text relative to the - * [x,y] coordinates. The default is LEFT. - */ - public enum Align { - /** - * The text is drawn to the right of the x,y origin - */ - LEFT (0), - /** - * The text is drawn centered horizontally on the x,y origin - */ - CENTER (1), - /** - * The text is drawn to the left of the x,y origin - */ - RIGHT (2); - - private Align(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - public Paint() { - this(0); - } - - /* - * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails. - */ - @Override - public void finalize() { } - - public Paint(int flags) { - setFlags(flags | DEFAULT_PAINT_FLAGS); - initFont(); - } - - public Paint(Paint paint) { - set(paint); - initFont(); - } - - @Override - public void reset() { - super.reset(); - } - - /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. - */ - public List<FontInfo> getFonts() { - return mFonts; - } - - private void initFont() { - mTypeface = Typeface.DEFAULT; - updateFontObject(); - } - - /** - * Update the {@link Font} object from the typeface, text size and scaling - */ - @SuppressWarnings("deprecation") - private void updateFontObject() { - if (mTypeface != null) { - // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); - - // create new font objects as well as FontMetrics, based on the current text size - // and skew info. - ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); - for (Font font : fonts) { - FontInfo info = new FontInfo(); - info.mFont = font.deriveFont(mTextSize); - if (mScaleX != 1.0 || mSkewX != 0) { - // TODO: support skew - info.mFont = info.mFont.deriveFont(new AffineTransform( - mScaleX, mSkewX, 0, 0, 1, 0)); - } - info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); - - infoList.add(info); - } - - mFonts = Collections.unmodifiableList(infoList); - } - } - - //---------------------------------------- - - public void set(Paint src) { - if (this != src) { - mColor = src.mColor; - mTextSize = src.mTextSize; - mScaleX = src.mScaleX; - mSkewX = src.mSkewX; - mAlign = src.mAlign; - mStyle = src.mStyle; - mFlags = src.mFlags; - - updateFontObject(); - - super.set(src); - } - } - - @Override - public void setCompatibilityScaling(float factor) { - super.setCompatibilityScaling(factor); - } - - @Override - public int getFlags() { - return mFlags; - } - - @Override - public void setFlags(int flags) { - mFlags = flags; - } - - @Override - public boolean isAntiAlias() { - return super.isAntiAlias(); - } - - @Override - public boolean isDither() { - return super.isDither(); - } - - @Override - public boolean isLinearText() { - return super.isLinearText(); - } - - @Override - public boolean isStrikeThruText() { - return super.isStrikeThruText(); - } - - @Override - public boolean isUnderlineText() { - return super.isUnderlineText(); - } - - @Override - public boolean isFakeBoldText() { - return super.isFakeBoldText(); - } - - @Override - public boolean isSubpixelText() { - return super.isSubpixelText(); - } - - @Override - public boolean isFilterBitmap() { - return super.isFilterBitmap(); - } - - /** - * Return the font's recommended interline spacing, given the Paint's - * settings for typeface, textSize, etc. If metrics is not null, return the - * fontmetric values in it. - * - * @param metrics If this object is not null, its fields are filled with - * the appropriate values given the paint's text attributes. - * @return the font's recommended interline spacing. - */ - public float getFontMetrics(FontMetrics metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - public int getFontMetricsInt(FontMetricsInt metrics) { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - if (metrics != null) { - // Android expects negative ascent so we invert the value from Java. - metrics.top = - javaMetrics.getMaxAscent(); - metrics.ascent = - javaMetrics.getAscent(); - metrics.descent = javaMetrics.getDescent(); - metrics.bottom = javaMetrics.getMaxDescent(); - metrics.leading = javaMetrics.getLeading(); - } - - return javaMetrics.getHeight(); - } - - return 0; - } - - /** - * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics - */ - public FontMetrics getFontMetrics() { - FontMetrics fm = new FontMetrics(); - getFontMetrics(fm); - return fm; - } - - /** - * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt - */ - public FontMetricsInt getFontMetricsInt() { - FontMetricsInt fm = new FontMetricsInt(); - getFontMetricsInt(fm); - return fm; - } - - - - @Override - public float getFontMetrics(_Original_Paint.FontMetrics metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - @Override - public Typeface setTypeface(Typeface typeface) { - if (typeface != null) { - mTypeface = typeface; - } else { - mTypeface = Typeface.DEFAULT; - } - - updateFontObject(); - - return typeface; - } - - @Override - public Typeface getTypeface() { - return super.getTypeface(); - } - - @Override - public int getColor() { - return mColor; - } - - @Override - public void setColor(int color) { - mColor = color; - } - - @Override - public void setARGB(int a, int r, int g, int b) { - super.setARGB(a, r, g, b); - } - - @Override - public void setAlpha(int alpha) { - mColor = (alpha << 24) | (mColor & 0x00FFFFFF); - } - - @Override - public int getAlpha() { - return mColor >>> 24; - } - - /** - * Set or clear the shader object. - * <p /> - * Pass null to clear any previous shader. - * As a convenience, the parameter passed is also returned. - * - * @param shader May be null. the new shader to be installed in the paint - * @return shader - */ - @Override - public Shader setShader(Shader shader) { - return mShader = shader; - } - - @Override - public Shader getShader() { - return super.getShader(); - } - - /** - * Set or clear the paint's colorfilter, returning the parameter. - * - * @param filter May be null. The new filter to be installed in the paint - * @return filter - */ - @Override - public ColorFilter setColorFilter(ColorFilter filter) { - mColorFilter = filter; - return filter; - } - - @Override - public ColorFilter getColorFilter() { - return super.getColorFilter(); - } - - /** - * Set or clear the xfermode object. - * <p /> - * Pass null to clear any previous xfermode. - * As a convenience, the parameter passed is also returned. - * - * @param xfermode May be null. The xfermode to be installed in the paint - * @return xfermode - */ - @Override - public Xfermode setXfermode(Xfermode xfermode) { - return mXfermode = xfermode; - } - - @Override - public Xfermode getXfermode() { - return super.getXfermode(); - } - - @Override - public Rasterizer setRasterizer(Rasterizer rasterizer) { - mRasterizer = rasterizer; - return rasterizer; - } - - @Override - public Rasterizer getRasterizer() { - return super.getRasterizer(); - } - - @Override - public void setShadowLayer(float radius, float dx, float dy, int color) { - // TODO Auto-generated method stub - } - - @Override - public void clearShadowLayer() { - super.clearShadowLayer(); - } - - public void setTextAlign(Align align) { - mAlign = align; - } - - @Override - public void setTextAlign(android.graphics._Original_Paint.Align align) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Align getTextAlign() { - return mAlign; - } - - public void setStyle(Style style) { - mStyle = style; - } - - @Override - public void setStyle(android.graphics._Original_Paint.Style style) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public Style getStyle() { - return mStyle; - } - - @Override - public void setDither(boolean dither) { - mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG; - } - - @Override - public void setAntiAlias(boolean aa) { - mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG; - } - - @Override - public void setFakeBoldText(boolean flag) { - mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG; - } - - @Override - public void setLinearText(boolean flag) { - mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG; - } - - @Override - public void setSubpixelText(boolean flag) { - mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG; - } - - @Override - public void setUnderlineText(boolean flag) { - mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG; - } - - @Override - public void setStrikeThruText(boolean flag) { - mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG; - } - - @Override - public void setFilterBitmap(boolean flag) { - mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG; - } - - @Override - public float getStrokeWidth() { - return mStrokeWidth; - } - - @Override - public void setStrokeWidth(float width) { - mStrokeWidth = width; - } - - @Override - public float getStrokeMiter() { - return mStrokeMiter; - } - - @Override - public void setStrokeMiter(float miter) { - mStrokeMiter = miter; - } - - @Override - public void setStrokeCap(android.graphics._Original_Paint.Cap cap) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeCap(Cap cap) { - mCap = cap; - } - - public Cap getStrokeCap() { - return mCap; - } - - @Override - public void setStrokeJoin(android.graphics._Original_Paint.Join join) { - throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); - } - - public void setStrokeJoin(Join join) { - mJoin = join; - } - - public Join getStrokeJoin() { - return mJoin; - } - - @Override - public boolean getFillPath(Path src, Path dst) { - return super.getFillPath(src, dst); - } - - @Override - public PathEffect setPathEffect(PathEffect effect) { - mPathEffect = effect; - return effect; - } - - @Override - public PathEffect getPathEffect() { - return super.getPathEffect(); - } - - @Override - public MaskFilter setMaskFilter(MaskFilter maskfilter) { - mMaskFilter = maskfilter; - return maskfilter; - } - - @Override - public MaskFilter getMaskFilter() { - return super.getMaskFilter(); - } - - /** - * Return the paint's text size. - * - * @return the paint's text size. - */ - @Override - public float getTextSize() { - return mTextSize; - } - - /** - * Set the paint's text size. This value must be > 0 - * - * @param textSize set the paint's text size. - */ - @Override - public void setTextSize(float textSize) { - mTextSize = textSize; - - updateFontObject(); - } - - /** - * Return the paint's horizontal scale factor for text. The default value - * is 1.0. - * - * @return the paint's scale factor in X for drawing/measuring text - */ - @Override - public float getTextScaleX() { - return mScaleX; - } - - /** - * Set the paint's horizontal scale factor for text. The default value - * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will - * stretch the text narrower. - * - * @param scaleX set the paint's scale in X for drawing/measuring text. - */ - @Override - public void setTextScaleX(float scaleX) { - mScaleX = scaleX; - - updateFontObject(); - } - - /** - * Return the paint's horizontal skew factor for text. The default value - * is 0. - * - * @return the paint's skew factor in X for drawing text. - */ - @Override - public float getTextSkewX() { - return mSkewX; - } - - /** - * Set the paint's horizontal skew factor for text. The default value - * is 0. For approximating oblique text, use values around -0.25. - * - * @param skewX set the paint's skew factor in X for drawing text. - */ - @Override - public void setTextSkewX(float skewX) { - mSkewX = skewX; - - updateFontObject(); - } - - @Override - public float getFontSpacing() { - return super.getFontSpacing(); - } - - /** - * Return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - * - * @return the distance above (negative) the baseline (ascent) based on the - * current typeface and text size. - */ - @Override - public float ascent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - // Android expects negative ascent so we invert the value from Java. - return - javaMetrics.getAscent(); - } - - return 0; - } - - /** - * Return the distance below (positive) the baseline (descent) based on the - * current typeface and text size. - * - * @return the distance below (positive) the baseline (descent) based on - * the current typeface and text size. - */ - @Override - public float descent() { - if (mFonts.size() > 0) { - java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; - return javaMetrics.getDescent(); - } - - return 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param index The index of the first character to start measuring - * @param count THe number of characters to measure, beginning with start - * @return The width of the text - */ - @Override - public float measureText(char[] text, int index, int count) { - // WARNING: the logic in this method is similar to Canvas.drawText. - // Any change to this method should be reflected in Canvas.drawText - 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 0; - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @param start The index of the first character to start measuring - * @param end 1 beyond the index of the last character to measure - * @return The width of the text - */ - @Override - public float measureText(String text, int start, int end) { - return measureText(text.toCharArray(), start, end - start); - } - - /** - * Return the width of the text. - * - * @param text The text to measure - * @return The width of the text - */ - @Override - public float measureText(String text) { - return measureText(text.toCharArray(), 0, text.length()); - } - - /* - * re-implement to call SpannableStringBuilder.measureText with a Paint object - * instead of an _Original_Paint - */ - @Override - public float measureText(CharSequence text, int start, int end) { - if (text instanceof String) { - return measureText((String)text, start, end); - } - if (text instanceof SpannedString || - text instanceof SpannableString) { - return measureText(text.toString(), start, end); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).measureText(start, end, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - float result = measureText(buf, 0, end - start); - TemporaryBuffer.recycle(buf); - return result; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param index The offset into text to begin measuring at - * @param count The number of maximum number of entries to measure. If count - * is negative, then the characters before index are measured - * in reverse order. This allows for measuring the end of - * string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(char[] text, int index, int count, - float maxWidth, float[] measuredWidth) { - int inc = count > 0 ? 1 : -1; - - int measureIndex = 0; - float measureAcc = 0; - for (int i = index ; i != index + count ; i += inc, measureIndex++) { - int start, end; - if (i < index) { - start = i; - end = index; - } else { - start = index; - end = i; - } - - // measure from start to end - float res = measureText(text, start, end - start + 1); - - if (measuredWidth != null) { - measuredWidth[measureIndex] = res; - } - - measureAcc += res; - if (res > maxWidth) { - // we should not return this char index, but since it's 0-based and we need - // to return a count, we simply return measureIndex; - return measureIndex; - } - - } - - return measureIndex; - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param measureForwards If true, measure forwards, starting at index. - * Otherwise, measure backwards, starting with the - * last character in the string. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(count). - */ - @Override - public int breakText(String text, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - return breakText(text, - 0 /* start */, text.length() /* end */, - measureForwards, maxWidth, measuredWidth); - } - - /** - * Measure the text, stopping early if the measured width exceeds maxWidth. - * Return the number of chars that were measured, and if measuredWidth is - * not null, return in it the actual width measured. - * - * @param text The text to measure - * @param start The offset into text to begin measuring at - * @param end The end of the text slice to measure. - * @param measureForwards If true, measure forwards, starting at start. - * Otherwise, measure backwards, starting with end. - * @param maxWidth The maximum width to accumulate. - * @param measuredWidth Optional. If not null, returns the actual width - * measured. - * @return The number of chars that were measured. Will always be <= - * abs(end - start). - */ - @Override - public int breakText(CharSequence text, int start, int end, boolean measureForwards, - float maxWidth, float[] measuredWidth) { - char[] buf = new char[end - start]; - int result; - - TextUtils.getChars(text, start, end, buf, 0); - - if (measureForwards) { - result = breakText(buf, 0, end - start, maxWidth, measuredWidth); - } else { - result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth); - } - - return result; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param index The index of the first char to to measure - * @param count The number of chars starting with index to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as count. - * @return the actual number of widths returned. - */ - @Override - public int getTextWidths(char[] text, int index, int count, - float[] widths) { - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length - || count > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // FIXME: handle multi-char characters. - // Need to figure out if the lengths of the width array takes into account - // multi-char characters. - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : mFonts) { - if (info.mFont.canDisplay(c)) { - widths[i] = info.mMetrics.charWidth(c); - found = true; - break; - } - } - - if (found == false) { - // we stop there. - return i; - } - } - - return count; - } - - return 0; - } - - /** - * Return the advance widths for the characters in the string. - * - * @param text The text to measure - * @param start The index of the first char to to measure - * @param end The end of the text slice to measure - * @param widths array to receive the advance widths of the characters. - * Must be at least a large as the text. - * @return the number of unichars in the specified text. - */ - @Override - public int getTextWidths(String text, int start, int end, float[] widths) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (end - start > widths.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - return getTextWidths(text.toCharArray(), start, end - start, widths); - } - - /* - * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object - * instead of an _Original_Paint - */ - @Override - public int getTextWidths(CharSequence text, int start, int end, float[] widths) { - if (text instanceof String) { - return getTextWidths((String)text, start, end, widths); - } - if (text instanceof SpannedString || text instanceof SpannableString) { - return getTextWidths(text.toString(), start, end, widths); - } - if (text instanceof SpannableStringBuilder) { - return ((SpannableStringBuilder)text).getTextWidths(start, end, widths, this); - } - - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - int result = getTextWidths(buf, 0, end - start, widths); - TemporaryBuffer.recycle(buf); - return result; - } - - @Override - public int getTextWidths(String text, float[] widths) { - return super.getTextWidths(text, widths); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting in - * the paint. - * - * @param text The text to retrieve the path from - * @param index The index of the first character in text - * @param count The number of characterss starting with index - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(char[] text, int index, int count, - float x, float y, Path path) { - - // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE - - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni()); - - throw new UnsupportedOperationException("IMPLEMENT AS NEEDED"); - } - - /** - * Return the path (outline) for the specified text. - * Note: just like Canvas.drawText, this will respect the Align setting - * in the paint. - * - * @param text The text to retrieve the path from - * @param start The first character in the text - * @param end 1 past the last charcter in the text - * @param x The x coordinate of the text's origin - * @param y The y coordinate of the text's origin - * @param path The path to receive the data describing the text. Must - * be allocated by the caller. - */ - @Override - public void getTextPath(String text, int start, int end, - float x, float y, Path path) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - - getTextPath(text.toCharArray(), start, end - start, x, y, path); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text String to measure and return its bounds - * @param start Index of the first char in the string to measure - * @param end 1 past the last char in the string measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(String text, int start, int end, Rect bounds) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - getTextBounds(text.toCharArray(), start, end - start, bounds); - } - - /** - * Return in bounds (allocated by the caller) the smallest rectangle that - * encloses all of the characters, with an implied origin at (0,0). - * - * @param text Array of chars to measure and return their unioned bounds - * @param index Index of the first char in the array to measure - * @param count The number of chars, beginning at index, to measure - * @param bounds Returns the unioned bounds of all the text. Must be - * allocated by the caller. - */ - @Override - public void getTextBounds(char[] text, int index, int count, Rect bounds) { - // FIXME - if (mFonts.size() > 0) { - if ((index | count) < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - if (bounds == null) { - throw new NullPointerException("need bounds Rect"); - } - - FontInfo mainInfo = mFonts.get(0); - - Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext); - bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); - } - } - - public static void finalizer(int foo) { - // pass - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java new file mode 100644 index 0000000..71d346a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter + * + * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PaintFlagsDrawFilter class. + * + * Because this extends {@link DrawFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by + * {@link DrawFilter_Delegate}. + * + * @see DrawFilter_Delegate + * + */ +public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Paint Flags Draw Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeConstructor(int clearBits, int setBits) { + PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java new file mode 100644 index 0000000..d4cf1f6 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -0,0 +1,1163 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Paint.FontMetrics; +import android.graphics.Paint.FontMetricsInt; + +import java.awt.BasicStroke; +import java.awt.Font; +import java.awt.Shape; +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; + +/** + * Delegate implementing the native methods of android.graphics.Paint + * + * Through the layoutlib_create tool, the original native methods of Paint have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Paint class. + * + * @see DelegateManager + * + */ +public class Paint_Delegate { + + /** + * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + */ + /*package*/ static final class FontInfo { + Font mFont; + java.awt.FontMetrics mMetrics; + } + + // ---- delegate manager ---- + private static final DelegateManager<Paint_Delegate> sManager = + new DelegateManager<Paint_Delegate>(Paint_Delegate.class); + + // ---- delegate helper data ---- + private List<FontInfo> mFonts; + private final FontRenderContext mFontContext = new FontRenderContext( + new AffineTransform(), true, true); + + // ---- delegate data ---- + private int mFlags; + private int mColor; + private int mStyle; + private int mCap; + private int mJoin; + private int mTextAlign; + private Typeface_Delegate mTypeface; + private float mStrokeWidth; + private float mStrokeMiter; + private float mTextSize; + private float mTextScaleX; + private float mTextSkewX; + + private Xfermode_Delegate mXfermode; + private ColorFilter_Delegate mColorFilter; + private Shader_Delegate mShader; + private PathEffect_Delegate mPathEffect; + private MaskFilter_Delegate mMaskFilter; + private Rasterizer_Delegate mRasterizer; + + // ---- Public Helper methods ---- + + public static Paint_Delegate getDelegate(int native_paint) { + return sManager.getDelegate(native_paint); + } + + /** + * Returns the list of {@link Font} objects. The first item is the main font, the rest + * are fall backs for characters not present in the main font. + */ + public List<FontInfo> getFonts() { + return mFonts; + } + + public boolean isAntiAliased() { + return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0; + } + + public boolean isFilterBitmap() { + return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0; + } + + public int getStyle() { + return mStyle; + } + + public int getColor() { + return mColor; + } + + public int getAlpha() { + return mColor >>> 24; + } + + public void setAlpha(int alpha) { + mColor = (alpha << 24) | (mColor & 0x00FFFFFF); + } + + public int getTextAlign() { + return mTextAlign; + } + + public float getStrokeWidth() { + return mStrokeWidth; + } + + /** + * returns the value of stroke miter needed by the java api. + */ + public float getJavaStrokeMiter() { + float miter = mStrokeMiter * mStrokeWidth; + if (miter < 1.f) { + miter = 1.f; + } + return miter; + } + + public int getJavaCap() { + switch (Paint.sCapArray[mCap]) { + case BUTT: + return BasicStroke.CAP_BUTT; + case ROUND: + return BasicStroke.CAP_ROUND; + default: + case SQUARE: + return BasicStroke.CAP_SQUARE; + } + } + + public int getJavaJoin() { + switch (Paint.sJoinArray[mJoin]) { + default: + case MITER: + return BasicStroke.JOIN_MITER; + case ROUND: + return BasicStroke.JOIN_ROUND; + case BEVEL: + return BasicStroke.JOIN_BEVEL; + } + } + + public Stroke getJavaStroke() { + if (mPathEffect != null) { + if (mPathEffect.isSupported()) { + Stroke stroke = mPathEffect.getStroke(this); + assert stroke != null; + if (stroke != null) { + return stroke; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT, + mPathEffect.getSupportMessage(), + null, null /*data*/); + } + } + + // if no custom stroke as been set, set the default one. + return new BasicStroke( + getStrokeWidth(), + getJavaCap(), + getJavaJoin(), + getJavaStrokeMiter()); + } + + /** + * Returns the {@link Xfermode} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Xfermode_Delegate getXfermode() { + return mXfermode; + } + + /** + * Returns the {@link ColorFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public ColorFilter_Delegate getColorFilter() { + return mColorFilter; + } + + /** + * Returns the {@link Shader} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Shader_Delegate getShader() { + return mShader; + } + + /** + * Returns the {@link MaskFilter} delegate or null if none have been set + * + * @return the delegate or null. + */ + public MaskFilter_Delegate getMaskFilter() { + return mMaskFilter; + } + + /** + * Returns the {@link Rasterizer} delegate or null if none have been set + * + * @return the delegate or null. + */ + public Rasterizer_Delegate getRasterizer() { + return mRasterizer; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int getFlags(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mFlags; + } + + @LayoutlibDelegate + /*package*/ static void setFlags(Paint thisPaint, int flags) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mFlags = flags; + } + + @LayoutlibDelegate + /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) { + setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter); + } + + @LayoutlibDelegate + /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) { + setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa); + } + + @LayoutlibDelegate + /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) { + setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText); + } + + @LayoutlibDelegate + /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) { + setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText); + } + + @LayoutlibDelegate + /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) { + setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText); + } + + @LayoutlibDelegate + /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) { + setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText); + } + + @LayoutlibDelegate + /*package*/ static void setDither(Paint thisPaint, boolean dither) { + setFlag(thisPaint, Paint.DITHER_FLAG, dither); + } + + @LayoutlibDelegate + /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) { + setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText); + } + + @LayoutlibDelegate + /*package*/ static int getColor(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.mColor; + } + + @LayoutlibDelegate + /*package*/ static void setColor(Paint thisPaint, int color) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mColor = color; + } + + @LayoutlibDelegate + /*package*/ static int getAlpha(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getAlpha(); + } + + @LayoutlibDelegate + /*package*/ static void setAlpha(Paint thisPaint, int a) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.setAlpha(a); + } + + @LayoutlibDelegate + /*package*/ static float getStrokeWidth(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeWidth; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeWidth(Paint thisPaint, float width) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeWidth = width; + } + + @LayoutlibDelegate + /*package*/ static float getStrokeMiter(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mStrokeMiter; + } + + @LayoutlibDelegate + /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mStrokeMiter = miter; + } + + @LayoutlibDelegate + /*package*/ static void setShadowLayer(Paint thisPaint, float radius, float dx, float dy, + int color) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.setShadowLayer is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static float getTextSize(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSize; + } + + @LayoutlibDelegate + /*package*/ static void setTextSize(Paint thisPaint, float textSize) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSize = textSize; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextScaleX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextScaleX; + } + + @LayoutlibDelegate + /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextScaleX = scaleX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float getTextSkewX(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 1.f; + } + + return delegate.mTextSkewX; + } + + @LayoutlibDelegate + /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mTextSkewX = skewX; + delegate.updateFontObject(); + } + + @LayoutlibDelegate + /*package*/ static float ascent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + // Android expects negative ascent so we invert the value from Java. + return - javaMetrics.getAscent(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float descent(Paint thisPaint) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + return javaMetrics.getDescent(); + } + + return 0; + + } + + @LayoutlibDelegate + /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + if (delegate.mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics; + if (fmi != null) { + // Android expects negative ascent so we invert the value from Java. + fmi.top = - javaMetrics.getMaxAscent(); + fmi.ascent = - javaMetrics.getAscent(); + fmi.descent = javaMetrics.getDescent(); + fmi.bottom = javaMetrics.getMaxDescent(); + fmi.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index, + int count) { + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + return delegate.measureText(text, index, count); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end) { + return native_measureText(thisPaint, text.toCharArray(), start, end - start); + } + + @LayoutlibDelegate + /*package*/ static float native_measureText(Paint thisPaint, String text) { + return native_measureText(thisPaint, text.toCharArray(), 0, text.length()); + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count, + float maxWidth, float[] measuredWidth) { + + // get the delegate + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return 0; + } + + int inc = count > 0 ? 1 : -1; + + int measureIndex = 0; + float measureAcc = 0; + for (int i = index; i != index + count; i += inc, measureIndex++) { + int start, end; + if (i < index) { + start = i; + end = index; + } else { + start = index; + end = i; + } + + // measure from start to end + float res = delegate.measureText(text, start, end - start + 1); + + if (measuredWidth != null) { + measuredWidth[measureIndex] = res; + } + + measureAcc += res; + if (res > maxWidth) { + // we should not return this char index, but since it's 0-based + // and we need to return a count, we simply return measureIndex; + return measureIndex; + } + + } + + return measureIndex; + } + + @LayoutlibDelegate + /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards, + float maxWidth, float[] measuredWidth) { + return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth, + measuredWidth); + } + + @LayoutlibDelegate + /*package*/ static int native_init() { + Paint_Delegate newDelegate = new Paint_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_initWithPaint(int paint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(paint); + if (delegate == null) { + return 0; + } + + Paint_Delegate newDelegate = new Paint_Delegate(delegate); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + // get the delegate from the native int. + Paint_Delegate delegate_dst = sManager.getDelegate(native_dst); + if (delegate_dst == null) { + return; + } + + // get the delegate from the native int. + Paint_Delegate delegate_src = sManager.getDelegate(native_src); + if (delegate_src == null) { + return; + } + + delegate_dst.set(delegate_src); + } + + @LayoutlibDelegate + /*package*/ static int native_getStyle(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void native_setStyle(int native_object, int style) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mStyle = style; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeCap(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mCap; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeCap(int native_object, int cap) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mCap = cap; + } + + @LayoutlibDelegate + /*package*/ static int native_getStrokeJoin(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mJoin; + } + + @LayoutlibDelegate + /*package*/ static void native_setStrokeJoin(int native_object, int join) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mJoin = join; + } + + @LayoutlibDelegate + /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) { + Paint_Delegate paint = sManager.getDelegate(native_object); + if (paint == null) { + return false; + } + + Path_Delegate srcPath = Path_Delegate.getDelegate(src); + if (srcPath == null) { + return true; + } + + Path_Delegate dstPath = Path_Delegate.getDelegate(dst); + if (dstPath == null) { + return true; + } + + Stroke stroke = paint.getJavaStroke(); + Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape()); + + dstPath.setJavaShape(strokeShape); + + // FIXME figure out the return value? + return true; + } + + @LayoutlibDelegate + /*package*/ static int native_setShader(int native_object, int shader) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return shader; + } + + delegate.mShader = Shader_Delegate.getDelegate(shader); + + return shader; + } + + @LayoutlibDelegate + /*package*/ static int native_setColorFilter(int native_object, int filter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return filter; + } + + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + + // since none of those are supported, display a fidelity warning right away + if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER, + delegate.mColorFilter.getSupportMessage(), null, null /*data*/); + } + + return filter; + } + + @LayoutlibDelegate + /*package*/ static int native_setXfermode(int native_object, int xfermode) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return xfermode; + } + + delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode); + + return xfermode; + } + + @LayoutlibDelegate + /*package*/ static int native_setPathEffect(int native_object, int effect) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return effect; + } + + delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect); + + return effect; + } + + @LayoutlibDelegate + /*package*/ static int native_setMaskFilter(int native_object, int maskfilter) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return maskfilter; + } + + delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, + delegate.mMaskFilter.getSupportMessage(), null, null /*data*/); + } + + return maskfilter; + } + + @LayoutlibDelegate + /*package*/ static int native_setTypeface(int native_object, int typeface) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.updateFontObject(); + return typeface; + } + + @LayoutlibDelegate + /*package*/ static int native_setRasterizer(int native_object, int rasterizer) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return rasterizer; + } + + delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer); + + // since none of those are supported, display a fidelity warning right away + if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER, + delegate.mRasterizer.getSupportMessage(), null, null /*data*/); + } + + return rasterizer; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextAlign(int native_object) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + return delegate.mTextAlign; + } + + @LayoutlibDelegate + /*package*/ static void native_setTextAlign(int native_object, int align) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return; + } + + delegate.mTextAlign = align; + } + + @LayoutlibDelegate + /*package*/ static float native_getFontMetrics(int native_paint, FontMetrics metrics) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_paint); + if (delegate == null) { + return 0.f; + } + + return delegate.getFontMetrics(metrics); + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, char[] text, int index, + int count, float[] widths) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(native_object); + if (delegate == null) { + return 0; + } + + 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 (widths != null) { + widths[i] = adv; + } + + found = true; + break; + } + } + + if (found == false) { + // no advance for this char. + if (widths != null) { + widths[i] = 0.f; + } + } + } + + return (int) totalAdvance; + } + + return 0; + } + + @LayoutlibDelegate + /*package*/ static int native_getTextWidths(int native_object, String text, int start, + int end, float[] widths) { + return native_getTextWidths(native_object, text.toCharArray(), start, end - start, widths); + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, + char[] text, int index, int count, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_getTextPath(int native_object, + String text, int start, int end, float x, float y, int path) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.getTextPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start, + int end, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bounds); + } + + @LayoutlibDelegate + /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index, + int count, Rect bounds) { + + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(nativePaint); + if (delegate == null) { + 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()); + } + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nativePaint) { + sManager.removeJavaReferenceFor(nativePaint); + } + + // ---- Private delegate/helper methods ---- + + /*package*/ Paint_Delegate() { + reset(); + } + + private Paint_Delegate(Paint_Delegate paint) { + set(paint); + } + + private void set(Paint_Delegate paint) { + mFlags = paint.mFlags; + mColor = paint.mColor; + mStyle = paint.mStyle; + mCap = paint.mCap; + mJoin = paint.mJoin; + mTextAlign = paint.mTextAlign; + mTypeface = paint.mTypeface; + mStrokeWidth = paint.mStrokeWidth; + mStrokeMiter = paint.mStrokeMiter; + mTextSize = paint.mTextSize; + mTextScaleX = paint.mTextScaleX; + mTextSkewX = paint.mTextSkewX; + mXfermode = paint.mXfermode; + mColorFilter = paint.mColorFilter; + mShader = paint.mShader; + mPathEffect = paint.mPathEffect; + mMaskFilter = paint.mMaskFilter; + mRasterizer = paint.mRasterizer; + updateFontObject(); + } + + private void reset() { + mFlags = Paint.DEFAULT_PAINT_FLAGS; + mColor = 0xFF000000; + mStyle = Paint.Style.FILL.nativeInt; + mCap = Paint.Cap.BUTT.nativeInt; + mJoin = Paint.Join.MITER.nativeInt; + mTextAlign = 0; + mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mStrokeWidth = 1.f; + mStrokeMiter = 4.f; + mTextSize = 20.f; + mTextScaleX = 1.f; + mTextSkewX = 0.f; + mXfermode = null; + mColorFilter = null; + mShader = null; + mPathEffect = null; + mMaskFilter = null; + mRasterizer = null; + updateFontObject(); + } + + /** + * Update the {@link Font} object from the typeface, text size and scaling + */ + @SuppressWarnings("deprecation") + private void updateFontObject() { + if (mTypeface != null) { + // Get the fonts from the TypeFace object. + List<Font> fonts = mTypeface.getFonts(); + + // create new font objects as well as FontMetrics, based on the current text size + // and skew info. + ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); + for (Font font : fonts) { + FontInfo info = new FontInfo(); + info.mFont = font.deriveFont(mTextSize); + if (mTextScaleX != 1.0 || mTextSkewX != 0) { + // TODO: support skew + info.mFont = info.mFont.deriveFont(new AffineTransform( + mTextScaleX, mTextSkewX, 0, 0, 1, 0)); + } + info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); + + infoList.add(info); + } + + mFonts = Collections.unmodifiableList(infoList); + } + } + + /*package*/ float measureText(char[] text, int index, int count) { + + // 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; + } + + private float getFontMetrics(FontMetrics metrics) { + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; + if (metrics != null) { + // Android expects negative ascent so we invert the value from Java. + metrics.top = - javaMetrics.getMaxAscent(); + metrics.ascent = - javaMetrics.getAscent(); + metrics.descent = javaMetrics.getDescent(); + metrics.bottom = javaMetrics.getMaxDescent(); + metrics.leading = javaMetrics.getLeading(); + } + + return javaMetrics.getHeight(); + } + + return 0; + } + + + + private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + if (flagValue) { + delegate.mFlags |= flagMask; + } else { + delegate.mFlags &= ~flagMask; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path.java b/tools/layoutlib/bridge/src/android/graphics/Path.java deleted file mode 100644 index 12d2cde..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Path.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2006 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.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Ellipse2D; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; -import java.awt.geom.Rectangle2D; - -/** - * The Path class encapsulates compound (multiple contour) geometric paths - * consisting of straight line segments, quadratic curves, and cubic curves. - * It can be drawn with canvas.drawPath(path, paint), either filled or stroked - * (based on the paint's Style), or it can be used for clipping or to draw - * text on a path. - */ -public class Path { - - private FillType mFillType = FillType.WINDING; - private GeneralPath mPath = new GeneralPath(); - - private float mLastX = 0; - private float mLastY = 0; - - //---------- Custom methods ---------- - - public Shape getAwtShape() { - return mPath; - } - - //---------- - - /** - * Create an empty path - */ - public Path() { - } - - /** - * Create a new path, copying the contents from the src path. - * - * @param src The path to copy from when initializing the new path - */ - public Path(Path src) { - mPath.append(src.mPath, false /* connect */); - } - - /** - * Clear any lines and curves from the path, making it empty. - * This does NOT change the fill-type setting. - */ - public void reset() { - mPath = new GeneralPath(); - } - - /** - * Rewinds the path: clears any lines and curves from the path but - * keeps the internal data structure for faster reuse. - */ - public void rewind() { - // FIXME - throw new UnsupportedOperationException(); - } - - /** Replace the contents of this with the contents of src. - */ - public void set(Path src) { - mPath.append(src.mPath, false /* connect */); - } - - /** Enum for the ways a path may be filled - */ - public enum FillType { - // these must match the values in SkPath.h - WINDING (GeneralPath.WIND_NON_ZERO, false), - EVEN_ODD (GeneralPath.WIND_EVEN_ODD, false), - INVERSE_WINDING (GeneralPath.WIND_NON_ZERO, true), - INVERSE_EVEN_ODD(GeneralPath.WIND_EVEN_ODD, true); - - FillType(int rule, boolean inverse) { - this.rule = rule; - this.inverse = inverse; - } - - final int rule; - final boolean inverse; - } - - /** - * Return the path's fill type. This defines how "inside" is - * computed. The default value is WINDING. - * - * @return the path's fill type - */ - public FillType getFillType() { - return mFillType; - } - - /** - * Set the path's fill type. This defines how "inside" is computed. - * - * @param ft The new fill type for this path - */ - public void setFillType(FillType ft) { - mFillType = ft; - mPath.setWindingRule(ft.rule); - } - - /** - * Returns true if the filltype is one of the INVERSE variants - * - * @return true if the filltype is one of the INVERSE variants - */ - public boolean isInverseFillType() { - return mFillType.inverse; - } - - /** - * Toggles the INVERSE state of the filltype - */ - public void toggleInverseFillType() { - switch (mFillType) { - case WINDING: - mFillType = FillType.INVERSE_WINDING; - break; - case EVEN_ODD: - mFillType = FillType.INVERSE_EVEN_ODD; - break; - case INVERSE_WINDING: - mFillType = FillType.WINDING; - break; - case INVERSE_EVEN_ODD: - mFillType = FillType.EVEN_ODD; - break; - } - } - - /** - * Returns true if the path is empty (contains no lines or curves) - * - * @return true if the path is empty (contains no lines or curves) - */ - public boolean isEmpty() { - return mPath.getCurrentPoint() == null; - } - - /** - * Returns true if the path specifies a rectangle. If so, and if rect is - * not null, set rect to the bounds of the path. If the path does not - * specify a rectangle, return false and ignore rect. - * - * @param rect If not null, returns the bounds of the path if it specifies - * a rectangle - * @return true if the path specifies a rectangle - */ - public boolean isRect(RectF rect) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Compute the bounds of the path, and write the answer into bounds. If the - * path contains 0 or 1 points, the bounds is set to (0,0,0,0) - * - * @param bounds Returns the computed bounds of the path - * @param exact If true, return the exact (but slower) bounds, else return - * just the bounds of all control points - */ - public void computeBounds(RectF bounds, boolean exact) { - Rectangle2D rect = mPath.getBounds2D(); - bounds.left = (float)rect.getMinX(); - bounds.right = (float)rect.getMaxX(); - bounds.top = (float)rect.getMinY(); - bounds.bottom = (float)rect.getMaxY(); - } - - /** - * Hint to the path to prepare for adding more points. This can allow the - * path to more efficiently allocate its storage. - * - * @param extraPtCount The number of extra points that may be added to this - * path - */ - public void incReserve(int extraPtCount) { - // pass - } - - /** - * Set the beginning of the next contour to the point (x,y). - * - * @param x The x-coordinate of the start of a new contour - * @param y The y-coordinate of the start of a new contour - */ - public void moveTo(float x, float y) { - mPath.moveTo(mLastX = x, mLastY = y); - } - - /** - * Set the beginning of the next contour relative to the last point on the - * previous contour. If there is no previous contour, this is treated the - * same as moveTo(). - * - * @param dx The amount to add to the x-coordinate of the end of the - * previous contour, to specify the start of a new contour - * @param dy The amount to add to the y-coordinate of the end of the - * previous contour, to specify the start of a new contour - */ - public void rMoveTo(float dx, float dy) { - dx += mLastX; - dy += mLastY; - mPath.moveTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a line from the last point to the specified point (x,y). - * If no moveTo() call has been made for this contour, the first point is - * automatically set to (0,0). - * - * @param x The x-coordinate of the end of a line - * @param y The y-coordinate of the end of a line - */ - public void lineTo(float x, float y) { - mPath.lineTo(mLastX = x, mLastY = y); - } - - /** - * Same as lineTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx The amount to add to the x-coordinate of the previous point on - * this contour, to specify a line - * @param dy The amount to add to the y-coordinate of the previous point on - * this contour, to specify a line - */ - public void rLineTo(float dx, float dy) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx += mLastX; - dy += mLastY; - mPath.lineTo(mLastX = dx, mLastY = dy); - } - - /** - * Add a quadratic bezier from the last point, approaching control point - * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for - * this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the control point on a quadratic curve - * @param y1 The y-coordinate of the control point on a quadratic curve - * @param x2 The x-coordinate of the end point on a quadratic curve - * @param y2 The y-coordinate of the end point on a quadratic curve - */ - public void quadTo(float x1, float y1, float x2, float y2) { - mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); - } - - /** - * Same as quadTo, but the coordinates are considered relative to the last - * point on this contour. If there is no previous point, then a moveTo(0,0) - * is inserted automatically. - * - * @param dx1 The amount to add to the x-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dy1 The amount to add to the y-coordinate of the last point on - * this contour, for the control point of a quadratic curve - * @param dx2 The amount to add to the x-coordinate of the last point on - * this contour, for the end point of a quadratic curve - * @param dy2 The amount to add to the y-coordinate of the last point on - * this contour, for the end point of a quadratic curve - */ - public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); - } - - /** - * Add a cubic bezier from the last point, approaching control points - * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been - * made for this contour, the first point is automatically set to (0,0). - * - * @param x1 The x-coordinate of the 1st control point on a cubic curve - * @param y1 The y-coordinate of the 1st control point on a cubic curve - * @param x2 The x-coordinate of the 2nd control point on a cubic curve - * @param y2 The y-coordinate of the 2nd control point on a cubic curve - * @param x3 The x-coordinate of the end point on a cubic curve - * @param y3 The y-coordinate of the end point on a cubic curve - */ - public void cubicTo(float x1, float y1, float x2, float y2, - float x3, float y3) { - mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); - } - - /** - * Same as cubicTo, but the coordinates are considered relative to the - * current point on this contour. If there is no previous point, then a - * moveTo(0,0) is inserted automatically. - */ - public void rCubicTo(float dx1, float dy1, float dx2, float dy2, - float dx3, float dy3) { - if (isEmpty()) { - mPath.moveTo(mLastX = 0, mLastY = 0); - } - dx1 += mLastX; - dy1 += mLastY; - dx2 += mLastX; - dy2 += mLastY; - dx3 += mLastX; - dy3 += mLastY; - mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); - } - - /** - * Append the specified arc to the path as a new contour. If the start of - * the path is different from the path's current last point, then an - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. The sweep angle is tread mod 360. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated - * mod 360. - * @param forceMoveTo If true, always begin a new contour with the arc - */ - public void arcTo(RectF oval, float startAngle, float sweepAngle, - boolean forceMoveTo) { - throw new UnsupportedOperationException(); - } - - /** - * Append the specified arc to the path as a new contour. If the start of - * the path is different from the path's current last point, then an - * automatic lineTo() is added to connect the current contour to the - * start of the arc. However, if the path is empty, then we call moveTo() - * with the first point of the arc. - * - * @param oval The bounds of oval defining shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - public void arcTo(RectF oval, float startAngle, float sweepAngle) { - throw new UnsupportedOperationException(); - } - - /** - * Close the current contour. If the current point is not equal to the - * first point of the contour, a line segment is automatically added. - */ - public void close() { - mPath.closePath(); - } - - /** - * Specifies how closed shapes (e.g. rects, ovals) are oriented when they - * are added to a path. - */ - public enum Direction { - /** clockwise */ - CW (0), // must match enum in SkPath.h - /** counter-clockwise */ - CCW (1); // must match enum in SkPath.h - - Direction(int ni) { - nativeInt = ni; - } - final int nativeInt; - } - - /** - * Add a closed rectangle contour to the path - * - * @param rect The rectangle to add as a closed contour to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(RectF rect, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - - addRect(rect.left, rect.top, rect.right, rect.bottom, dir); - } - - /** - * Add a closed rectangle contour to the path - * - * @param left The left side of a rectangle to add to the path - * @param top The top of a rectangle to add to the path - * @param right The right side of a rectangle to add to the path - * @param bottom The bottom of a rectangle to add to the path - * @param dir The direction to wind the rectangle's contour - */ - public void addRect(float left, float top, float right, float bottom, - Direction dir) { - moveTo(left, top); - - switch (dir) { - case CW: - lineTo(right, top); - lineTo(right, bottom); - lineTo(left, bottom); - break; - case CCW: - lineTo(left, bottom); - lineTo(right, bottom); - lineTo(right, top); - break; - } - - close(); - } - - /** - * Add a closed oval contour to the path - * - * @param oval The bounds of the oval to add as a closed contour to the path - * @param dir The direction to wind the oval's contour - */ - public void addOval(RectF oval, Direction dir) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - - // FIXME Need to support direction - Ellipse2D ovalShape = new Ellipse2D.Float(oval.left, oval.top, oval.width(), oval.height()); - - mPath.append(ovalShape, false /* connect */); - } - - /** - * Add a closed circle contour to the path - * - * @param x The x-coordinate of the center of a circle to add to the path - * @param y The y-coordinate of the center of a circle to add to the path - * @param radius The radius of a circle to add to the path - * @param dir The direction to wind the circle's contour - */ - public void addCircle(float x, float y, float radius, Direction dir) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add the specified arc to the path as a new contour. - * - * @param oval The bounds of oval defining the shape and size of the arc - * @param startAngle Starting angle (in degrees) where the arc begins - * @param sweepAngle Sweep angle (in degrees) measured clockwise - */ - public void addArc(RectF oval, float startAngle, float sweepAngle) { - if (oval == null) { - throw new NullPointerException("need oval parameter"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a closed round-rectangle contour to the path - * - * @param rect The bounds of a round-rectangle to add to the path - * @param rx The x-radius of the rounded corners on the round-rectangle - * @param ry The y-radius of the rounded corners on the round-rectangle - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a closed round-rectangle contour to the path. Each corner receives - * two radius values [X, Y]. The corners are ordered top-left, top-right, - * bottom-right, bottom-left - * - * @param rect The bounds of a round-rectangle to add to the path - * @param radii Array of 8 values, 4 pairs of [X,Y] radii - * @param dir The direction to wind the round-rectangle's contour - */ - public void addRoundRect(RectF rect, float[] radii, Direction dir) { - if (rect == null) { - throw new NullPointerException("need rect parameter"); - } - if (radii.length < 8) { - throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); - } - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Add a copy of src to the path, offset by (dx,dy) - * - * @param src The path to add as a new contour - * @param dx The amount to translate the path in X as it is added - */ - public void addPath(Path src, float dx, float dy) { - PathIterator iterator = src.mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); - mPath.append(iterator, false /* connect */); - } - - /** - * Add a copy of src to the path - * - * @param src The path that is appended to the current path - */ - public void addPath(Path src) { - addPath(src, 0, 0); - } - - /** - * Add a copy of src to the path, transformed by matrix - * - * @param src The path to add as a new contour - */ - public void addPath(Path src, Matrix matrix) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - * @param dst The translated path is written here. If this is null, then - * the original path is modified. - */ - public void offset(float dx, float dy, Path dst) { - GeneralPath newPath = new GeneralPath(); - - PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); - - newPath.append(iterator, false /* connect */); - - if (dst != null) { - dst.mPath = newPath; - } else { - mPath = newPath; - } - } - - /** - * Offset the path by (dx,dy), returning true on success - * - * @param dx The amount in the X direction to offset the entire path - * @param dy The amount in the Y direction to offset the entire path - */ - public void offset(float dx, float dy) { - offset(dx, dy, null /* dst */); - } - - /** - * Sets the last point of the path. - * - * @param dx The new X coordinate for the last point - * @param dy The new Y coordinate for the last point - */ - public void setLastPoint(float dx, float dy) { - mLastX = dx; - mLastY = dy; - } - - /** - * Transform the points in this path by matrix, and write the answer - * into dst. If dst is null, then the the original path is modified. - * - * @param matrix The matrix to apply to the path - * @param dst The transformed path is written here. If dst is null, - * then the the original path is modified - */ - public void transform(Matrix matrix, Path dst) { - // FIXME - throw new UnsupportedOperationException(); - } - - /** - * Transform the points in this path by matrix. - * - * @param matrix The matrix to apply to the path - */ - public void transform(Matrix matrix) { - transform(matrix, null /* dst */); - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java new file mode 100644 index 0000000..c448f0e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathDashPathEffect + * + * Through the layoutlib_create tool, the original native methods of PathDashPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PathDashPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class PathDashPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Path Dash Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int native_path, float advance, float phase, + int native_style) { + PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java new file mode 100644 index 0000000..bd2b6de --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.PathEffect + * + * Through the layoutlib_create tool, the original native methods of PathEffect have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PathEffect class. + * + * This also serve as a base class for all PathEffect delegate classes. + * + * @see DelegateManager + * + */ +public abstract class PathEffect_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<PathEffect_Delegate> sManager = + new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static PathEffect_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract Stroke getStroke(Paint_Delegate paint); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_patheffect) { + sManager.removeJavaReferenceFor(native_patheffect); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java new file mode 100644 index 0000000..6c9f48f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Path.Direction; +import android.graphics.Path.FillType; + +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Path + * + * Through the layoutlib_create tool, the original native methods of Path have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Path class. + * + * @see DelegateManager + * + */ +public final class Path_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Path_Delegate> sManager = + new DelegateManager<Path_Delegate>(Path_Delegate.class); + + // ---- delegate data ---- + private FillType mFillType = FillType.WINDING; + private GeneralPath mPath = new GeneralPath(); + + private float mLastX = 0; + private float mLastY = 0; + + // ---- Public Helper methods ---- + + public static Path_Delegate getDelegate(int nPath) { + return sManager.getDelegate(nPath); + } + + public Shape getJavaShape() { + return mPath; + } + + public void setJavaShape(Shape shape) { + mPath.reset(); + mPath.append(shape, false /*connect*/); + } + + public void reset() { + mPath.reset(); + } + + public void setPathIterator(PathIterator iterator) { + mPath.reset(); + mPath.append(iterator, false /*connect*/); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int init1() { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int init2(int nPath) { + // create the delegate + Path_Delegate newDelegate = new Path_Delegate(); + + // get the delegate to copy, which could be null if nPath is 0 + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate != null) { + newDelegate.set(pathDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_reset(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mPath.reset(); + } + + @LayoutlibDelegate + /*package*/ static void native_rewind(int nPath) { + // call out to reset since there's nothing to optimize in + // terms of data structs. + native_reset(nPath); + } + + @LayoutlibDelegate + /*package*/ static void native_set(int native_dst, int native_src) { + Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst); + if (pathDstDelegate == null) { + return; + } + + Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src); + if (pathSrcDelegate == null) { + return; + } + + pathDstDelegate.set(pathSrcDelegate); + } + + @LayoutlibDelegate + /*package*/ static int native_getFillType(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return 0; + } + + return pathDelegate.mFillType.nativeInt; + } + + @LayoutlibDelegate + /*package*/ static void native_setFillType(int nPath, int ft) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mFillType = Path.sFillTypeArray[ft]; + } + + @LayoutlibDelegate + /*package*/ static boolean native_isEmpty(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return true; + } + + return pathDelegate.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean native_isRect(int nPath, RectF rect) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return false; + } + + // create an Area that can test if the path is a rect + Area area = new Area(pathDelegate.mPath); + if (area.isRectangular()) { + if (rect != null) { + pathDelegate.fillBounds(rect); + } + + return true; + } + + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_computeBounds(int nPath, RectF bounds) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.fillBounds(bounds); + } + + @LayoutlibDelegate + /*package*/ static void native_incReserve(int nPath, int extraPtCount) { + // since we use a java2D path, there's no way to pre-allocate new points, + // so we do nothing. + } + + @LayoutlibDelegate + /*package*/ static void native_moveTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.moveTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rMoveTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_lineTo(int nPath, float x, float y) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.lineTo(x, y); + } + + @LayoutlibDelegate + /*package*/ static void native_rLineTo(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rLineTo(dx, dy); + } + + @LayoutlibDelegate + /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.quadTo(x1, y1, x2, y2); + } + + @LayoutlibDelegate + /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rQuadTo(dx1, dy1, dx2, dy2); + } + + @LayoutlibDelegate + /*package*/ static void native_cubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_rCubicTo(int nPath, float x1, float y1, + float x2, float y2, float x3, float y3) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3); + } + + @LayoutlibDelegate + /*package*/ static void native_arcTo(int nPath, RectF oval, + float startAngle, float sweepAngle, boolean forceMoveTo) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); + } + + @LayoutlibDelegate + /*package*/ static void native_close(int nPath) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.close(); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, RectF rect, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addRect(int nPath, + float left, float top, float right, float bottom, int dir) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.addRect(left, top, right, bottom, dir); + } + + @LayoutlibDelegate + /*package*/ static void native_addOval(int nPath, RectF oval, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addOval is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addCircle is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addArc(int nPath, RectF oval, + float startAngle, float sweepAngle) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addArc is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF rect, + float rx, float ry, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addRoundRect is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src) { + native_addPath(nPath, src, 0, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_addPath(int nPath, int src, int matrix) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.addPath is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + // could be null if the int is 0; + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.offset(dx, dy, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_offset(int nPath, float dx, float dy) { + native_offset(nPath, dx, dy, 0); + } + + @LayoutlibDelegate + /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + pathDelegate.mLastX = dx; + pathDelegate.mLastY = dy; + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix, + int dst_path) { + Path_Delegate pathDelegate = sManager.getDelegate(nPath); + if (pathDelegate == null) { + return; + } + + Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix); + if (matrixDelegate == null) { + return; + } + + // this can be null if dst_path is 0 + Path_Delegate dstDelegate = sManager.getDelegate(dst_path); + + pathDelegate.transform(matrixDelegate, dstDelegate); + } + + @LayoutlibDelegate + /*package*/ static void native_transform(int nPath, int matrix) { + native_transform(nPath, matrix, 0); + } + + @LayoutlibDelegate + /*package*/ static void finalizer(int nPath) { + sManager.removeJavaReferenceFor(nPath); + } + + + // ---- Private helper methods ---- + + private void set(Path_Delegate delegate) { + mPath.reset(); + setFillType(delegate.mFillType); + mPath.append(delegate.mPath, false /*connect*/); + } + + private void setFillType(FillType fillType) { + mFillType = fillType; + mPath.setWindingRule(getWindingRule(fillType)); + } + + /** + * Returns the Java2D winding rules matching a given Android {@link FillType}. + * @param type the android fill type + * @return the matching java2d winding rule. + */ + private static int getWindingRule(FillType type) { + switch (type) { + case WINDING: + case INVERSE_WINDING: + return GeneralPath.WIND_NON_ZERO; + case EVEN_ODD: + case INVERSE_EVEN_ODD: + return GeneralPath.WIND_EVEN_ODD; + } + + assert false; + throw new IllegalArgumentException(); + } + + private static Direction getDirection(int direction) { + for (Direction d : Direction.values()) { + if (direction == d.nativeInt) { + return d; + } + } + + assert false; + return null; + } + + /** + * Returns whether the path is empty. + * @return true if the path is empty. + */ + private boolean isEmpty() { + return mPath.getCurrentPoint() == null; + } + + /** + * Fills the given {@link RectF} with the path bounds. + * @param bounds the RectF to be filled. + */ + private void fillBounds(RectF bounds) { + Rectangle2D rect = mPath.getBounds2D(); + bounds.left = (float)rect.getMinX(); + bounds.right = (float)rect.getMaxX(); + bounds.top = (float)rect.getMinY(); + bounds.bottom = (float)rect.getMaxY(); + } + + /** + * Set the beginning of the next contour to the point (x,y). + * + * @param x The x-coordinate of the start of a new contour + * @param y The y-coordinate of the start of a new contour + */ + private void moveTo(float x, float y) { + mPath.moveTo(mLastX = x, mLastY = y); + } + + /** + * Set the beginning of the next contour relative to the last point on the + * previous contour. If there is no previous contour, this is treated the + * same as moveTo(). + * + * @param dx The amount to add to the x-coordinate of the end of the + * previous contour, to specify the start of a new contour + * @param dy The amount to add to the y-coordinate of the end of the + * previous contour, to specify the start of a new contour + */ + private void rMoveTo(float dx, float dy) { + dx += mLastX; + dy += mLastY; + mPath.moveTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a line from the last point to the specified point (x,y). + * If no moveTo() call has been made for this contour, the first point is + * automatically set to (0,0). + * + * @param x The x-coordinate of the end of a line + * @param y The y-coordinate of the end of a line + */ + private void lineTo(float x, float y) { + mPath.lineTo(mLastX = x, mLastY = y); + } + + /** + * Same as lineTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx The amount to add to the x-coordinate of the previous point on + * this contour, to specify a line + * @param dy The amount to add to the y-coordinate of the previous point on + * this contour, to specify a line + */ + private void rLineTo(float dx, float dy) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx += mLastX; + dy += mLastY; + mPath.lineTo(mLastX = dx, mLastY = dy); + } + + /** + * Add a quadratic bezier from the last point, approaching control point + * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for + * this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the control point on a quadratic curve + * @param y1 The y-coordinate of the control point on a quadratic curve + * @param x2 The x-coordinate of the end point on a quadratic curve + * @param y2 The y-coordinate of the end point on a quadratic curve + */ + private void quadTo(float x1, float y1, float x2, float y2) { + mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2); + } + + /** + * Same as quadTo, but the coordinates are considered relative to the last + * point on this contour. If there is no previous point, then a moveTo(0,0) + * is inserted automatically. + * + * @param dx1 The amount to add to the x-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dy1 The amount to add to the y-coordinate of the last point on + * this contour, for the control point of a quadratic curve + * @param dx2 The amount to add to the x-coordinate of the last point on + * this contour, for the end point of a quadratic curve + * @param dy2 The amount to add to the y-coordinate of the last point on + * this contour, for the end point of a quadratic curve + */ + private void rQuadTo(float dx1, float dy1, float dx2, float dy2) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2); + } + + /** + * Add a cubic bezier from the last point, approaching control points + * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been + * made for this contour, the first point is automatically set to (0,0). + * + * @param x1 The x-coordinate of the 1st control point on a cubic curve + * @param y1 The y-coordinate of the 1st control point on a cubic curve + * @param x2 The x-coordinate of the 2nd control point on a cubic curve + * @param y2 The y-coordinate of the 2nd control point on a cubic curve + * @param x3 The x-coordinate of the end point on a cubic curve + * @param y3 The y-coordinate of the end point on a cubic curve + */ + private void cubicTo(float x1, float y1, float x2, float y2, + float x3, float y3) { + mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); + } + + /** + * Same as cubicTo, but the coordinates are considered relative to the + * current point on this contour. If there is no previous point, then a + * moveTo(0,0) is inserted automatically. + */ + private void rCubicTo(float dx1, float dy1, float dx2, float dy2, + float dx3, float dy3) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } + dx1 += mLastX; + dy1 += mLastY; + dx2 += mLastX; + dy2 += mLastY; + dx3 += mLastX; + dy3 += mLastY; + mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3); + } + + /** + * Append the specified arc to the path as a new contour. If the start of + * the path is different from the path's current last point, then an + * automatic lineTo() is added to connect the current contour to the + * start of the arc. However, if the path is empty, then we call moveTo() + * with the first point of the arc. The sweep angle is tread mod 360. + * + * @param oval The bounds of oval defining shape and size of the arc + * @param startAngle Starting angle (in degrees) where the arc begins + * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated + * mod 360. + * @param forceMoveTo If true, always begin a new contour with the arc + */ + private void arcTo(RectF oval, float startAngle, float sweepAngle, + boolean forceMoveTo) { + Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle, + sweepAngle, Arc2D.OPEN); + mPath.append(arc, true /*connect*/); + + resetLastPointFromPath(); + } + + /** + * Close the current contour. If the current point is not equal to the + * first point of the contour, a line segment is automatically added. + */ + private void close() { + mPath.closePath(); + } + + private void resetLastPointFromPath() { + Point2D last = mPath.getCurrentPoint(); + mLastX = (float) last.getX(); + mLastY = (float) last.getY(); + } + + /** + * Add a closed rectangle contour to the path + * + * @param left The left side of a rectangle to add to the path + * @param top The top of a rectangle to add to the path + * @param right The right side of a rectangle to add to the path + * @param bottom The bottom of a rectangle to add to the path + * @param dir The direction to wind the rectangle's contour + */ + private void addRect(float left, float top, float right, float bottom, + int dir) { + moveTo(left, top); + + Direction direction = getDirection(dir); + + switch (direction) { + case CW: + lineTo(right, top); + lineTo(right, bottom); + lineTo(left, bottom); + break; + case CCW: + lineTo(left, bottom); + lineTo(right, bottom); + lineTo(right, top); + break; + } + + close(); + + resetLastPointFromPath(); + } + + /** + * Offset the path by (dx,dy), returning true on success + * + * @param dx The amount in the X direction to offset the entire path + * @param dy The amount in the Y direction to offset the entire path + * @param dst The translated path is written here. If this is null, then + * the original path is modified. + */ + public void offset(float dx, float dy, Path_Delegate dst) { + GeneralPath newPath = new GeneralPath(); + + PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy)); + + newPath.append(iterator, false /*connect*/); + + if (dst != null) { + dst.mPath = newPath; + } else { + mPath = newPath; + } + } + + /** + * Transform the points in this path by matrix, and write the answer + * into dst. If dst is null, then the the original path is modified. + * + * @param matrix The matrix to apply to the path + * @param dst The transformed path is written here. If dst is null, + * then the the original path is modified + */ + public void transform(Matrix_Delegate matrix, Path_Delegate dst) { + if (matrix.hasPerspective()) { + assert false; + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE, + "android.graphics.Path#transform() only " + + "supports affine transformations.", null, null /*data*/); + } + + GeneralPath newPath = new GeneralPath(); + + PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform()); + + newPath.append(iterator, false /*connect*/); + + if (dst != null) { + dst.mPath = newPath; + } else { + mPath = newPath; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java new file mode 100644 index 0000000..4ab044b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PixelXorXfermode + * + * Through the layoutlib_create tool, the original native methods of PixelXorXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PixelXorXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + * @see Xfermode_Delegate + */ +public class PixelXorXfermode_Delegate extends Xfermode_Delegate { + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Composite getComposite(int alpha) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Pixel XOR Xfermodes are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int opColor) { + PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java new file mode 100644 index 0000000..65c65a1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter + * + * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter have + * been replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PorterDuffColorFilter class. + * + * Because this extends {@link ColorFilter_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the Shader classes will be added to the manager + * owned by {@link ColorFilter_Delegate}. + * + * @see ColorFilter_Delegate + * + */ +public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "PorterDuff Color Filters are not supported."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) { + PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java new file mode 100644 index 0000000..4301c1a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.AlphaComposite; +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.PorterDuffXfermode + * + * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original PorterDuffXfermode class. + * + * Because this extends {@link Xfermode_Delegate}, there's no need to use a + * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by + * {@link Xfermode_Delegate}. + * + */ +public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { + + // ---- delegate data ---- + + private final int mMode; + + // ---- Public Helper methods ---- + + public PorterDuff.Mode getMode() { + return getPorterDuffMode(mMode); + } + + @Override + public Composite getComposite(int alpha) { + return getComposite(getPorterDuffMode(mMode), alpha); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getSupportMessage() { + // no message since isSupported returns true; + return null; + } + + public static PorterDuff.Mode getPorterDuffMode(int mode) { + for (PorterDuff.Mode m : PorterDuff.Mode.values()) { + if (m.nativeInt == mode) { + return m; + } + } + + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/); + assert false; + return PorterDuff.Mode.SRC_OVER; + } + + public static Composite getComposite(PorterDuff.Mode mode, int alpha) { + float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f; + switch (mode) { + case CLEAR: + return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha); + case DARKEN: + break; + case DST: + return AlphaComposite.getInstance(AlphaComposite.DST, falpha); + case DST_ATOP: + return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha); + case DST_IN: + return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha); + case DST_OUT: + return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha); + case DST_OVER: + return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha); + case LIGHTEN: + break; + case MULTIPLY: + break; + case SCREEN: + break; + case SRC: + return AlphaComposite.getInstance(AlphaComposite.SRC, falpha); + case SRC_ATOP: + return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha); + case SRC_IN: + return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha); + case SRC_OUT: + return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha); + case SRC_OVER: + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + case XOR: + return AlphaComposite.getInstance(AlphaComposite.XOR, falpha); + } + + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unsupported PorterDuff Mode: %s", mode.name()), + null, null /*data*/); + + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreateXfermode(int mode) { + PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- + + private PorterDuffXfermode_Delegate(int mode) { + mMode = mode; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java deleted file mode 100644 index 4409a80..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2008 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; - -public class RadialGradient extends GradientShader { - - private RadialGradientPaint mPaint; - - /** - * Create a shader that draws a radial gradient given the center and radius. - * - * @param x The x-coordinate of the center of the radius - * @param y The y-coordinate of the center of the radius - * @param radius Must be positive. The radius of the circle for this - * gradient - * @param colors The colors to be distributed between the center and edge of - * the circle - * @param positions May be NULL. The relative position of each corresponding - * color in the colors array. If this is NULL, the the colors are - * distributed evenly between the center and edge of the circle. - * @param tile The Shader tiling mode - */ - public RadialGradient(float x, float y, float radius, int colors[], float positions[], - TileMode tile) { - super(colors, positions); - if (radius <= 0) { - throw new IllegalArgumentException("radius must be > 0"); - } - - mPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile); - } - - /** - * Create a shader that draws a radial gradient given the center and radius. - * - * @param x The x-coordinate of the center of the radius - * @param y The y-coordinate of the center of the radius - * @param radius Must be positive. The radius of the circle for this - * gradient - * @param color0 The color at the center of the circle. - * @param color1 The color at the edge of the circle. - * @param tile The Shader tiling mode - */ - public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile) { - this(x, y, radius, new int[] { color0, color1 }, null /* positions */, tile); - } - - @Override - java.awt.Paint getJavaPaint() { - return mPaint; - } - - private static class RadialGradientPaint extends GradientPaint { - - private final float mX; - private final float mY; - private final float mRadius; - - public RadialGradientPaint(float x, float y, float radius, int[] colors, float[] positions, TileMode mode) { - super(colors, positions, mode); - mX = x; - mY = y; - mRadius = radius; - } - - public java.awt.PaintContext createContext( - java.awt.image.ColorModel colorModel, - java.awt.Rectangle deviceBounds, - java.awt.geom.Rectangle2D userBounds, - java.awt.geom.AffineTransform xform, - java.awt.RenderingHints hints) { - precomputeGradientColors(); - return new RadialGradientPaintContext(colorModel); - } - - private class RadialGradientPaintContext implements java.awt.PaintContext { - - private final java.awt.image.ColorModel mColorModel; - - public RadialGradientPaintContext(java.awt.image.ColorModel colorModel) { - mColorModel = colorModel; - } - - public void dispose() { - } - - public java.awt.image.ColorModel getColorModel() { - return mColorModel; - } - - public java.awt.image.Raster getRaster(int x, int y, int w, int h) { - java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, - java.awt.image.BufferedImage.TYPE_INT_ARGB); - - int[] data = new int[w*h]; - - // compute distance from each point to the center, and figure out the distance from - // it. - int index = 0; - for (int iy = 0 ; iy < h ; iy++) { - for (int ix = 0 ; ix < w ; ix++) { - float _x = x + ix - mX; - float _y = y + iy - mY; - float distance = (float) Math.sqrt(_x * _x + _y * _y); - - data[index++] = getGradientColor(distance / mRadius); - } - } - - image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); - - return image.getRaster(); - } - - } - } - -} diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java new file mode 100644 index 0000000..bdc0ab1 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.RadialGradient + * + * Through the layoutlib_create tool, the original native methods of RadialGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original RadialGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class RadialGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, float radius, + int colors[], float positions[], int tileMode) { + RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(x, y, radius, + colors, positions, Shader_Delegate.getTileMode(tileMode)); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, float radius, + int color0, int color1, int tileMode) { + return nativeCreate1(x, y, radius, new int[] { color0, color1 }, null /*positions*/, + tileMode); + } + + // ---- Private delegate/helper methods ---- + + /** + * Create a shader that draws a radial gradient given the center and radius. + * + * @param x The x-coordinate of the center of the radius + * @param y The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the circle for this + * gradient + * @param colors The colors to be distributed between the center and edge of + * the circle + * @param positions May be NULL. The relative position of each corresponding + * color in the colors array. If this is NULL, the the colors are + * distributed evenly between the center and edge of the circle. + * @param tile The Shader tiling mode + */ + private RadialGradient_Delegate(float x, float y, float radius, int colors[], float positions[], + TileMode tile) { + super(colors, positions); + mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile); + } + + private class RadialGradientPaint extends GradientPaint { + + private final float mX; + private final float mY; + private final float mRadius; + + public RadialGradientPaint(float x, float y, float radius, + int[] colors, float[] positions, TileMode mode) { + super(colors, positions, mode); + mX = x; + mY = y; + mRadius = radius; + } + + public java.awt.PaintContext createContext( + java.awt.image.ColorModel colorModel, + java.awt.Rectangle deviceBounds, + java.awt.geom.Rectangle2D userBounds, + java.awt.geom.AffineTransform xform, + java.awt.RenderingHints hints) { + precomputeGradientColors(); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in RadialGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel); + } + + private class RadialGradientPaintContext implements java.awt.PaintContext { + + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; + private final java.awt.image.ColorModel mColorModel; + + public RadialGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; + mColorModel = colorModel; + } + + public void dispose() { + } + + public java.awt.image.ColorModel getColorModel() { + return mColorModel; + } + + public java.awt.image.Raster getRaster(int x, int y, int w, int h) { + java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB); + + int[] data = new int[w*h]; + + // compute distance from each point to the center, and figure out the distance from + // it. + int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; + for (int iy = 0 ; iy < h ; iy++) { + for (int ix = 0 ; ix < w ; ix++) { + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mX; + pt1[1] = pt2[1] - mY; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float _x = pt2[0]; + float _y = pt2[1]; + float distance = (float) Math.sqrt(_x * _x + _y * _y); + + data[index++] = getGradientColor(distance / mRadius); + } + } + + image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); + + return image.getRaster(); + } + + } + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java new file mode 100644 index 0000000..2812b6b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.graphics.Rasterizer + * + * Through the layoutlib_create tool, the original native methods of Rasterizer have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Rasterizer class. + * + * This also serve as a base class for all Rasterizer delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Rasterizer_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Rasterizer_Delegate> sManager = + new DelegateManager<Rasterizer_Delegate>(Rasterizer_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Rasterizer_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java new file mode 100644 index 0000000..b0ee5c2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.os.Parcel; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; + +/** + * Delegate implementing the native methods of android.graphics.Region + * + * Through the layoutlib_create tool, the original native methods of Region have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Region class. + * + * This also serve as a base class for all Region delegate classes. + * + * @see DelegateManager + * + */ +public class Region_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Region_Delegate> sManager = + new DelegateManager<Region_Delegate>(Region_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Area mArea = new Area(); + + // ---- Public Helper methods ---- + + public static Region_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + public Area getJavaArea() { + return mArea; + } + + /** + * Combines two {@link Shape} into another one (actually an {@link Area}), according + * to the given {@link Region.Op}. + * + * If the Op is not one that combines two shapes, then this return null + * + * @param shape1 the firt shape to combine which can be null if there's no original clip. + * @param shape2 the 2nd shape to combine + * @param regionOp the operande for the combine + * @return a new area or null. + */ + public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) { + if (regionOp == Region.Op.DIFFERENCE.nativeInt) { + // if shape1 is null (empty), then the result is null. + if (shape1 == null) { + return null; + } + + // result is always a new area. + Area result = new Area(shape1); + result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.INTERSECT.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.UNION.nativeInt) { + // if shape1 is null, then the result is simply shape2. + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.XOR.nativeInt) { + // if shape1 is null, then the result is simply shape2 + if (shape1 == null) { + return new Area(shape2); + } + + // result is always a new area. + Area result = new Area(shape1); + result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2)); + return result; + + } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) { + // result is always a new area. + Area result = new Area(shape2); + + if (shape1 != null) { + result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1)); + } + + return result; + } + + return null; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static boolean isEmpty(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isEmpty(); + } + + @LayoutlibDelegate + /*package*/ static boolean isRect(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isRectangular(); + } + + @LayoutlibDelegate + /*package*/ static boolean isComplex(Region thisRegion) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return true; + } + + return regionDelegate.mArea.isSingular() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean contains(Region thisRegion, int x, int y) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.contains(x, y); + } + + @LayoutlibDelegate + /*package*/ static boolean quickContains(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isRectangular() && + regionDelegate.mArea.contains(left, top, right - left, bottom - top); + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, + int left, int top, int right, int bottom) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false; + } + + @LayoutlibDelegate + /*package*/ static boolean quickReject(Region thisRegion, Region rgn) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return false; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion); + if (targetRegionDelegate == null) { + return false; + } + + return regionDelegate.mArea.isEmpty() || + regionDelegate.mArea.getBounds().intersects( + targetRegionDelegate.mArea.getBounds()) == false; + + } + + @LayoutlibDelegate + /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.translate(dx, dy); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static void scale(Region thisRegion, float scale, Region dst) { + Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion); + if (regionDelegate == null) { + return; + } + + Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion); + if (targetRegionDelegate == null) { + return; + } + + if (regionDelegate.mArea.isEmpty()) { + targetRegionDelegate.mArea = new Area(); + } else { + targetRegionDelegate.mArea = new Area(regionDelegate.mArea); + AffineTransform mtx = new AffineTransform(); + mtx.scale(scale, scale); + targetRegionDelegate.mArea.transform(mtx); + } + } + + @LayoutlibDelegate + /*package*/ static int nativeConstructor() { + Region_Delegate newDelegate = new Region_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_region) { + sManager.removeJavaReferenceFor(native_region); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate srcRegion = sManager.getDelegate(native_src); + if (srcRegion == null) { + return true; + } + + dstRegion.mArea.reset(); + dstRegion.mArea.add(srcRegion.mArea); + + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetRect(int native_dst, + int left, int top, int right, int bottom) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top)); + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return true; + } + + dstRegion.mArea = new Area(path.getJavaShape()); + + Region_Delegate clip = sManager.getDelegate(native_clip); + if (clip != null) { + dstRegion.mArea.subtract(clip.getJavaArea()); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return true; + } + + Rectangle bounds = region.mArea.getBounds(); + if (bounds.isEmpty()) { + rect.left = rect.top = rect.right = rect.bottom = 0; + return false; + } + + rect.left = bounds.x; + rect.top = bounds.y; + rect.right = bounds.x + bounds.width; + rect.bottom = bounds.y + bounds.height; + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) { + Region_Delegate region = sManager.getDelegate(native_region); + if (region == null) { + return false; + } + + Path_Delegate path = Path_Delegate.getDelegate(native_path); + if (path == null) { + return false; + } + + if (region.mArea.isEmpty()) { + path.reset(); + return false; + } + + path.setPathIterator(region.mArea.getPathIterator(new AffineTransform())); + return true; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int left, int top, int right, int bottom, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(left, top, right - left, bottom - top), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) { + Region_Delegate region = sManager.getDelegate(native_dst); + if (region == null) { + return false; + } + + region.mArea = combineShapes(region.mArea, + new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op); + + assert region.mArea != null; + if (region.mArea != null) { + region.mArea = new Area(); + } + + return region.mArea.getBounds().isEmpty() == false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeOp(int native_dst, + int native_region1, int native_region2, int op) { + Region_Delegate dstRegion = sManager.getDelegate(native_dst); + if (dstRegion == null) { + return true; + } + + Region_Delegate region1 = sManager.getDelegate(native_region1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_region2); + if (region2 == null) { + return false; + } + + dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op); + + assert dstRegion.mArea != null; + if (dstRegion.mArea != null) { + dstRegion.mArea = new Area(); + } + + return dstRegion.mArea.getBounds().isEmpty() == false; + + } + + @LayoutlibDelegate + /*package*/ static int nativeCreateFromParcel(Parcel p) { + // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only + // used during aidl call so really this should not be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be created from parcels.", + null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeWriteToParcel(int native_region, + Parcel p) { + // This is only called when sending a region through aidl, so really this should not + // be called. + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "AIDL is not suppored, and therefore Regions cannot be written to parcels.", + null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeEquals(int native_r1, int native_r2) { + Region_Delegate region1 = sManager.getDelegate(native_r1); + if (region1 == null) { + return false; + } + + Region_Delegate region2 = sManager.getDelegate(native_r2); + if (region2 == null) { + return false; + } + + return region1.mArea.equals(region2.mArea); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java deleted file mode 100644 index 0cc5940..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Shader.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2008 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; - - - -/** - * Shader is the based class for objects that return horizontal spans of colors - * during drawing. A subclass of Shader is installed in a Paint calling - * paint.setShader(shader). After that any object (other than a bitmap) that is - * drawn with that paint will get its color(s) from the shader. - */ -public abstract class Shader { - - private final Matrix mMatrix = new Matrix(); - - public enum TileMode { - /** - * replicate the edge color if the shader draws outside of its - * original bounds - */ - CLAMP (0), - /** - * repeat the shader's image horizontally and vertically - */ - REPEAT (1), - /** - * repeat the shader's image horizontally and vertically, alternating - * mirror images so that adjacent images always seam - */ - MIRROR (2); - - TileMode(int nativeInt) { - this.nativeInt = nativeInt; - } - final int nativeInt; - } - - /** - * Return true if the shader has a non-identity local matrix. - * @param localM If not null, it is set to the shader's local matrix. - * @return true if the shader has a non-identity local matrix - */ - public boolean getLocalMatrix(Matrix localM) { - if (localM != null) { - localM.set(mMatrix); - } - - return !mMatrix.isIdentity(); - } - - /** - * Set the shader's local matrix. Passing null will reset the shader's - * matrix to identity - * @param localM The shader's new local matrix, or null to specify identity - */ - public void setLocalMatrix(Matrix localM) { - if (localM != null) { - mMatrix.set(localM); - } else { - mMatrix.reset(); - } - } - - /** - * Returns a java.awt.Paint object matching this shader. - */ - abstract java.awt.Paint getJavaPaint(); -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java new file mode 100644 index 0000000..c6dd54b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Shader.TileMode; + +/** + * Delegate implementing the native methods of android.graphics.Shader + * + * Through the layoutlib_create tool, the original native methods of Shader have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Shader class. + * + * This also serve as a base class for all Shader delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Shader_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Shader_Delegate> sManager = + new DelegateManager<Shader_Delegate>(Shader_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + private Matrix_Delegate mLocalMatrix = null; + + // ---- Public Helper methods ---- + + public static Shader_Delegate getDelegate(int nativeShader) { + return sManager.getDelegate(nativeShader); + } + + /** + * Returns the {@link TileMode} matching the given int. + * @param tileMode the tile mode int value + * @return the TileMode enum. + */ + public static TileMode getTileMode(int tileMode) { + for (TileMode tm : TileMode.values()) { + if (tm.nativeInt == tileMode) { + return tm; + } + } + + assert false; + return TileMode.CLAMP; + } + + public abstract java.awt.Paint getJavaPaint(); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void nativeDestructor(int native_shader) { + sManager.removeJavaReferenceFor(native_shader); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeGetLocalMatrix(int native_shader, int matrix_instance) { + // get the delegate from the native int. + Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); + if (shaderDelegate == null) { + return false; + } + + // get the (optional) out matrix. + Matrix_Delegate outMatrixDelegate = Matrix_Delegate.getDelegate(matrix_instance); + + if (shaderDelegate.mLocalMatrix == null || shaderDelegate.mLocalMatrix.isIdentity()) { + if (outMatrixDelegate != null) { + outMatrixDelegate.reset(); + } + return false; + } + + if (outMatrixDelegate != null) { + outMatrixDelegate.set(shaderDelegate.mLocalMatrix); + } + + return true; + } + + + @LayoutlibDelegate + /*package*/ static void nativeSetLocalMatrix(int native_shader, int matrix_instance) { + // get the delegate from the native int. + Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); + if (shaderDelegate == null) { + return; + } + + shaderDelegate.mLocalMatrix = Matrix_Delegate.getDelegate(matrix_instance); + } + + // ---- Private delegate/helper methods ---- + + protected java.awt.geom.AffineTransform getLocalMatrix() { + if (mLocalMatrix != null) { + return mLocalMatrix.getAffineTransform(); + } + + return new java.awt.geom.AffineTransform(); + } + +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java new file mode 100644 index 0000000..410df0c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Stroke; + +/** + * Delegate implementing the native methods of android.graphics.SumPathEffect + * + * Through the layoutlib_create tool, the original native methods of SumPathEffect have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original SumPathEffect class. + * + * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}. + * + * @see PathEffect_Delegate + * + */ +public class SumPathEffect_Delegate extends PathEffect_Delegate { + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + @Override + public Stroke getStroke(Paint_Delegate paint) { + // FIXME + return null; + } + + @Override + public boolean isSupported() { + return false; + } + + @Override + public String getSupportMessage() { + return "Sum Path Effects are not supported in Layout Preview mode."; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate(int first, int second) { + SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate(); + return sManager.addNewDelegate(newDelegate); + } + + // ---- Private delegate/helper methods ---- +} diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index 87036ed..f70f9cf 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2010 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. @@ -16,9 +16,53 @@ package android.graphics; -public class SweepGradient extends GradientShader { +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - private SweepGradientPaint mPaint; +/** + * Delegate implementing the native methods of android.graphics.SweepGradient + * + * Through the layoutlib_create tool, the original native methods of SweepGradient have been + * replaced by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original SweepGradient class. + * + * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, + * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. + * + * @see Shader_Delegate + * + */ +public class SweepGradient_Delegate extends Gradient_Delegate { + + // ---- delegate data ---- + private java.awt.Paint mJavaPaint; + + // ---- Public Helper methods ---- + + @Override + public java.awt.Paint getJavaPaint() { + return mJavaPaint; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static int nativeCreate1(float x, float y, int colors[], float positions[]) { + SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(x, y, colors, positions); + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static int nativeCreate2(float x, float y, int color0, int color1) { + return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); + } + + // ---- Private delegate/helper methods ---- /** * A subclass of Shader that draws a sweep gradient around a center point. @@ -34,36 +78,19 @@ public class SweepGradient extends GradientShader { * If positions is NULL, then the colors are automatically * spaced evenly. */ - public SweepGradient(float cx, float cy, + private SweepGradient_Delegate(float cx, float cy, int colors[], float positions[]) { super(colors, positions); - - mPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); + mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions); } - /** - * A subclass of Shader that draws a sweep gradient around a center point. - * - * @param cx The x-coordinate of the center - * @param cy The y-coordinate of the center - * @param color0 The color to use at the start of the sweep - * @param color1 The color to use at the end of the sweep - */ - public SweepGradient(float cx, float cy, int color0, int color1) { - this(cx, cy, new int[] { color0, color1}, null /*positions*/); - } - - @Override - java.awt.Paint getJavaPaint() { - return mPaint; - } - - private static class SweepGradientPaint extends GradientPaint { + private class SweepGradientPaint extends GradientPaint { private final float mCx; private final float mCy; - public SweepGradientPaint(float cx, float cy, int[] colors, float[] positions) { + public SweepGradientPaint(float cx, float cy, int[] colors, + float[] positions) { super(colors, positions, null /*tileMode*/); mCx = cx; mCy = cy; @@ -76,14 +103,40 @@ public class SweepGradient extends GradientShader { java.awt.geom.AffineTransform xform, java.awt.RenderingHints hints) { precomputeGradientColors(); - return new SweepGradientPaintContext(colorModel); + + java.awt.geom.AffineTransform canvasMatrix; + try { + canvasMatrix = xform.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + canvasMatrix = new java.awt.geom.AffineTransform(); + } + + java.awt.geom.AffineTransform localMatrix = getLocalMatrix(); + try { + localMatrix = localMatrix.createInverse(); + } catch (java.awt.geom.NoninvertibleTransformException e) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, + "Unable to inverse matrix in SweepGradient", e, null /*data*/); + localMatrix = new java.awt.geom.AffineTransform(); + } + + return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel); } private class SweepGradientPaintContext implements java.awt.PaintContext { + private final java.awt.geom.AffineTransform mCanvasMatrix; + private final java.awt.geom.AffineTransform mLocalMatrix; private final java.awt.image.ColorModel mColorModel; - public SweepGradientPaintContext(java.awt.image.ColorModel colorModel) { + public SweepGradientPaintContext( + java.awt.geom.AffineTransform canvasMatrix, + java.awt.geom.AffineTransform localMatrix, + java.awt.image.ColorModel colorModel) { + mCanvasMatrix = canvasMatrix; + mLocalMatrix = localMatrix; mColorModel = colorModel; } @@ -103,10 +156,23 @@ public class SweepGradient extends GradientShader { // compute angle from each point to the center, and figure out the distance from // it. int index = 0; + float[] pt1 = new float[2]; + float[] pt2 = new float[2]; for (int iy = 0 ; iy < h ; iy++) { for (int ix = 0 ; ix < w ; ix++) { - float dx = x + ix - mCx; - float dy = y + iy - mCy; + // handle the canvas transform + pt1[0] = x + ix; + pt1[1] = y + iy; + mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); + + // handle the local matrix + pt1[0] = pt2[0] - mCx; + pt1[1] = pt2[1] - mCy; + mLocalMatrix.transform(pt1, 0, pt2, 0, 1); + + float dx = pt2[0]; + float dy = pt2[1]; + float angle; if (dx == 0) { angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2); @@ -135,6 +201,4 @@ public class SweepGradient extends GradientShader { } } - } - diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface.java b/tools/layoutlib/bridge/src/android/graphics/Typeface.java deleted file mode 100644 index af3adb5..0000000 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.bridge.FontLoader; - -import android.content.res.AssetManager; - -import java.awt.Font; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Re-implementation of Typeface over java.awt - */ -public class Typeface { - private static final String DEFAULT_FAMILY = "sans-serif"; - private static final int[] styleBuffer = new int[1]; - - /** The default NORMAL typeface object */ - public static Typeface DEFAULT; - /** - * The default BOLD typeface object. Note: this may be not actually be - * bold, depending on what fonts are installed. Call getStyle() to know - * for sure. - */ - public static Typeface DEFAULT_BOLD; - /** The NORMAL style of the default sans serif typeface. */ - public static Typeface SANS_SERIF; - /** The NORMAL style of the default serif typeface. */ - public static Typeface SERIF; - /** The NORMAL style of the default monospace typeface. */ - public static Typeface MONOSPACE; - - private static Typeface[] sDefaults; - private static FontLoader mFontLoader; - - private final int mStyle; - private final List<Font> mFonts; - private final String mFamily; - - // Style - public static final int NORMAL = _Original_Typeface.NORMAL; - public static final int BOLD = _Original_Typeface.BOLD; - public static final int ITALIC = _Original_Typeface.ITALIC; - public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC; - - /** - * Returns the underlying {@link Font} objects. The first item in the list is the real - * font. Any other items are fallback fonts for characters not found in the first one. - */ - public List<Font> getFonts() { - return mFonts; - } - - /** Returns the typeface's intrinsic style attributes */ - public int getStyle() { - return mStyle; - } - - /** Returns true if getStyle() has the BOLD bit set. */ - public final boolean isBold() { - return (getStyle() & BOLD) != 0; - } - - /** Returns true if getStyle() has the ITALIC bit set. */ - public final boolean isItalic() { - return (getStyle() & ITALIC) != 0; - } - - /** - * Create a typeface object given a family name, and option style information. - * If null is passed for the name, then the "default" font will be chosen. - * The resulting typeface object can be queried (getStyle()) to discover what - * its "real" style characteristics are. - * - * @param familyName May be null. The name of the font family. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(String familyName, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(familyName, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(familyName, styleBuffer[0], list); - } - - return null; - } - - /** - * Create a typeface object that best matches the specified existing - * typeface and the specified Style. Use this call if you want to pick a new - * style from the same family of an existing typeface object. If family is - * null, this selects from the default font's family. - * - * @param family May be null. The name of the existing type face. - * @param style The style (normal, bold, italic) of the typeface. - * e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC - * @return The best matching typeface. - */ - public static Typeface create(Typeface family, int style) { - styleBuffer[0] = style; - Font font = mFontLoader.getFont(family.mFamily, styleBuffer); - if (font != null) { - ArrayList<Font> list = new ArrayList<Font>(); - list.add(font); - list.addAll(mFontLoader.getFallBackFonts()); - return new Typeface(family.mFamily, styleBuffer[0], list); - } - - return null; - } - - /** - * Returns one of the default typeface objects, based on the specified style - * - * @return the default typeface that corresponds to the style - */ - public static Typeface defaultFromStyle(int style) { - return sDefaults[style]; - } - - /** - * Create a new typeface from the specified font data. - * @param mgr The application's asset manager - * @param path The file name of the font data in the assets directory - * @return The new typeface. - */ - public static Typeface createFromAsset(AssetManager mgr, String path) { - return null; - //return new Typeface(nativeCreateFromAsset(mgr, path)); - } - - // don't allow clients to call this directly - private Typeface(String family, int style, List<Font> fonts) { - mFamily = family; - mFonts = Collections.unmodifiableList(fonts); - mStyle = style; - } - - public static void init(FontLoader fontLoader) { - mFontLoader = fontLoader; - - DEFAULT = create(DEFAULT_FAMILY, NORMAL); - DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD); - SANS_SERIF = create("sans-serif", NORMAL); - SERIF = create("serif", NORMAL); - MONOSPACE = create("monospace", NORMAL); - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create(DEFAULT_FAMILY, ITALIC), - create(DEFAULT_FAMILY, BOLD_ITALIC), - }; - - /* - DEFAULT = create((String)null, 0); - DEFAULT_BOLD = create((String)null, Typeface.BOLD); - SANS_SERIF = create("sans-serif", 0); - SERIF = create("serif", 0); - MONOSPACE = create("monospace", 0); - - sDefaults = new Typeface[] { - DEFAULT, - DEFAULT_BOLD, - create((String)null, Typeface.ITALIC), - create((String)null, Typeface.BOLD_ITALIC), - };*/ - } -} diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java new file mode 100644 index 0000000..0f084f7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.FontLoader; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.AssetManager; + +import java.awt.Font; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Delegate implementing the native methods of android.graphics.Typeface + * + * Through the layoutlib_create tool, the original native methods of Typeface have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Typeface class. + * + * @see DelegateManager + * + */ +public final class Typeface_Delegate { + + // ---- delegate manager ---- + private static final DelegateManager<Typeface_Delegate> sManager = + new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); + + // ---- delegate helper data ---- + private static final String DEFAULT_FAMILY = "sans-serif"; + private static final int[] STYLE_BUFFER = new int[1]; + + private static FontLoader sFontLoader; + private static final List<Typeface_Delegate> sPostInitDelegate = + new ArrayList<Typeface_Delegate>(); + + // ---- delegate data ---- + + private final String mFamily; + private int mStyle; + private List<Font> mFonts; + + + // ---- Public Helper methods ---- + + public static synchronized void init(FontLoader fontLoader) { + sFontLoader = fontLoader; + + for (Typeface_Delegate delegate : sPostInitDelegate) { + delegate.init(); + } + sPostInitDelegate.clear(); + } + + public static Typeface_Delegate getDelegate(int nativeTypeface) { + return sManager.getDelegate(nativeTypeface); + } + + public static List<Font> getFonts(Typeface typeface) { + return getFonts(typeface.native_instance); + } + + public static List<Font> getFonts(int native_int) { + Typeface_Delegate delegate = sManager.getDelegate(native_int); + if (delegate == null) { + return null; + } + + return delegate.getFonts(); + } + + public List<Font> getFonts() { + return mFonts; + } + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreate(String familyName, int style) { + if (familyName == null) { + familyName = DEFAULT_FAMILY; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromTypeface(int native_instance, int style) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); + if (sFontLoader != null) { + newDelegate.init(); + } else { + // font loader has not been initialized yet, add the delegate to a list of delegates + // to init when the font loader is initialized. + // There won't be any rendering before this happens anyway. + sPostInitDelegate.add(newDelegate); + } + + return sManager.addNewDelegate(newDelegate); + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromAsset(AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static synchronized int nativeCreateFromFile(String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Typeface.createFromFile() is not supported.", null /*throwable*/, null /*data*/); + return 0; + } + + @LayoutlibDelegate + /*package*/ static void nativeUnref(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + @LayoutlibDelegate + /*package*/ static int nativeGetStyle(int native_instance) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + return 0; + } + + return delegate.mStyle; + } + + @LayoutlibDelegate + /*package*/ static void setGammaForText(float blackGamma, float whiteGamma) { + // This is for device testing only: pass + } + + // ---- Private delegate/helper methods ---- + + private Typeface_Delegate(String family, int style) { + mFamily = family; + mStyle = style; + } + + private void init() { + STYLE_BUFFER[0] = mStyle; + Font font = sFontLoader.getFont(mFamily, STYLE_BUFFER); + if (font != null) { + List<Font> list = new ArrayList<Font>(); + list.add(font); + list.addAll(sFontLoader.getFallBackFonts()); + mFonts = Collections.unmodifiableList(list); + mStyle = STYLE_BUFFER[0]; + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java new file mode 100644 index 0000000..962d69c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.Composite; + +/** + * Delegate implementing the native methods of android.graphics.Xfermode + * + * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original Xfermode class. + * + * This also serve as a base class for all Xfermode delegate classes. + * + * @see DelegateManager + * + */ +public abstract class Xfermode_Delegate { + + // ---- delegate manager ---- + protected static final DelegateManager<Xfermode_Delegate> sManager = + new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class); + + // ---- delegate helper data ---- + + // ---- delegate data ---- + + // ---- Public Helper methods ---- + + public static Xfermode_Delegate getDelegate(int native_instance) { + return sManager.getDelegate(native_instance); + } + + public abstract Composite getComposite(int alpha); + public abstract boolean isSupported(); + public abstract String getSupportMessage(); + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static void finalizer(int native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + // ---- Private delegate/helper methods ---- + +} diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java new file mode 100644 index 0000000..ff82a5e --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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.os; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Map; + +/** + * Delegate implementing the native methods of android.os.Build + * + * Through the layoutlib_create tool, the original native methods of Build have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class Build_Delegate { + + @LayoutlibDelegate + /*package*/ static String getString(String property) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(property); + if (value != null) { + return value; + } + + return Build.UNKNOWN; + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java new file mode 100644 index 0000000..2152c8a --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 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.os; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + + +/** + * Delegate overriding selected methods of android.os.Handler + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + * + */ +public class Handler_Delegate { + + // -------- Delegate methods + + @LayoutlibDelegate + /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { + // get the callback + IHandlerCallback callback = sCallbacks.get(); + if (callback != null) { + callback.sendMessageAtTime(handler, msg, uptimeMillis); + } + return true; + } + + // -------- Delegate implementation + + public interface IHandlerCallback { + void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis); + } + + private final static ThreadLocal<IHandlerCallback> sCallbacks = + new ThreadLocal<IHandlerCallback>(); + + public static void setCallback(IHandlerCallback callback) { + sCallbacks.set(callback); + } + +} diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java new file mode 100644 index 0000000..63711a7 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 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.os; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate implementing the native methods of android.os.SystemClock + * + * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class SystemClock_Delegate { + private static long sBootTime = System.currentTimeMillis(); + + @LayoutlibDelegate + /*package*/ static boolean setCurrentTimeMillis(long millis) { + return true; + } + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * <b>Note:</b> This value may get reset occasionally (before it would + * otherwise wrap around). + * + * @return milliseconds of non-sleep uptime since boot. + */ + @LayoutlibDelegate + /*package*/ static long uptimeMillis() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @return elapsed milliseconds since boot. + */ + @LayoutlibDelegate + /*package*/ static long elapsedRealtime() { + return System.currentTimeMillis() - sBootTime; + } + + /** + * Returns milliseconds running in the current thread. + * + * @return elapsed milliseconds in the thread + */ + @LayoutlibDelegate + /*package*/ static long currentThreadTimeMillis() { + return System.currentTimeMillis(); + } +} diff --git a/tools/layoutlib/bridge/src/android/util/FloatMath.java b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java index aae44f2..1df78c2 100644 --- a/tools/layoutlib/bridge/src/android/util/FloatMath.java +++ b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java @@ -16,20 +16,23 @@ package android.util; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + /** - * Reimplements _Original_FloatMath with the standard libraries. - * - * Math routines similar to those found in {@link java.lang.Math}. Performs - * computations on {@code float} values directly without incurring the overhead - * of conversions to and from {@code double}. + * Delegate implementing the native methods of android.util.FloatMath + * + * Through the layoutlib_create tool, the original native methods of FloatMath have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. * - * <p>On one platform, {@code FloatMath.sqrt(100)} executes in one third of the - * time required by {@code java.lang.Math.sqrt(100)}.</p> */ -public class FloatMath { +/*package*/ final class FloatMath_Delegate { /** Prevents instantiation. */ - private FloatMath() {} + private FloatMath_Delegate() {} /** * Returns the float conversion of the most positive (i.e. closest to @@ -38,7 +41,8 @@ public class FloatMath { * @param value to be converted * @return the floor of value */ - public static float floor(float value) { + @LayoutlibDelegate + /*package*/ static float floor(float value) { return (float)Math.floor(value); } @@ -49,7 +53,8 @@ public class FloatMath { * @param value to be converted * @return the ceiling of value */ - public static float ceil(float value) { + @LayoutlibDelegate + /*package*/ static float ceil(float value) { return (float)Math.ceil(value); } @@ -59,7 +64,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the sine of angle */ - public static float sin(float angle) { + @LayoutlibDelegate + /*package*/ static float sin(float angle) { return (float)Math.sin(angle); } @@ -69,7 +75,8 @@ public class FloatMath { * @param angle to compute the cosine of, in radians * @return the cosine of angle */ - public static float cos(float angle) { + @LayoutlibDelegate + /*package*/ static float cos(float angle) { return (float)Math.cos(angle); } @@ -80,7 +87,8 @@ public class FloatMath { * @param value to compute sqrt of * @return the square root of value */ - public static float sqrt(float value) { + @LayoutlibDelegate + /*package*/ static float sqrt(float value) { return (float)Math.sqrt(value); } } diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java new file mode 100644 index 0000000..d5266a5 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2011 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.view; + +import com.android.layoutlib.bridge.android.BridgeInflater; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Xml; + +import java.io.IOException; + +/** + * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater} + * + * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced + * by calls to methods of the same name in this delegate class. + * + * Generally we don't want to copy-paste a huge method like that just for one small features, + * but because this is done after this platform is final and the next version (Honeycomb) has a + * better mechanism (or slightly less copy-paste), maintenance of this duplicated code is not + * a problem. + * + */ +public class LayoutInflater_Delegate { + + @LayoutlibDelegate + /*package*/ static void parseInclude(LayoutInflater thisInflater, + XmlPullParser parser, View parent, AttributeSet attrs) + throws XmlPullParserException, IOException { + + int type; + + if (parent instanceof ViewGroup) { + final int layout = attrs.getAttributeResourceValue(null, "layout", 0); + if (layout == 0) { + final String value = attrs.getAttributeValue(null, "layout"); + if (value == null) { + throw new InflateException("You must specifiy a layout in the" + + " include tag: <include layout=\"@layout/layoutID\" />"); + } else { + throw new InflateException("You must specifiy a valid layout " + + "reference. The layout ID " + value + " is not valid."); + } + } else { + final XmlResourceParser childParser = + thisInflater.getContext().getResources().getLayout(layout); + + try { + final AttributeSet childAttrs = Xml.asAttributeSet(childParser); + + while ((type = childParser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty. + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(childParser.getPositionDescription() + + ": No start tag found!"); + } + + final String childName = childParser.getName(); + + if (LayoutInflater.TAG_MERGE.equals(childName)) { + // ---- START MODIFICATIONS ---- + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(true); + } + // ---- END MODIFICATIONS ---- + + // Inflate all children. + thisInflater.rInflate(childParser, parent, childAttrs); + + // ---- START MODIFICATIONS ---- + if (thisInflater instanceof BridgeInflater) { + ((BridgeInflater) thisInflater).setIsInMerge(false); + } + // ---- END MODIFICATIONS ---- + } else { + final View view = thisInflater.createViewFromTag(childName, childAttrs); + final ViewGroup group = (ViewGroup) parent; + + // We try to load the layout params set in the <include /> tag. If + // they don't exist, we will rely on the layout params set in the + // included XML file. + // During a layoutparams generation, a runtime exception is thrown + // if either layout_width or layout_height is missing. We catch + // this exception and set localParams accordingly: true means we + // successfully loaded layout params from the <include /> tag, + // false means we need to rely on the included layout params. + ViewGroup.LayoutParams params = null; + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + params = group.generateLayoutParams(childAttrs); + } finally { + if (params != null) { + view.setLayoutParams(params); + } + } + + // Inflate all children. + thisInflater.rInflate(childParser, view, childAttrs); + + // Attempt to override the included layout's android:id with the + // one set on the <include /> tag itself. + TypedArray a = thisInflater.mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.View, 0, 0); + int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID); + // While we're at it, let's try to override android:visibility. + int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1); + a.recycle(); + + if (id != View.NO_ID) { + view.setId(id); + } + + switch (visibility) { + case 0: + view.setVisibility(View.VISIBLE); + break; + case 1: + view.setVisibility(View.INVISIBLE); + break; + case 2: + view.setVisibility(View.GONE); + break; + } + + group.addView(view); + } + } finally { + childParser.close(); + } + } + } else { + throw new InflateException("<include /> can only be used inside of a ViewGroup"); + } + + final int currentDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { + // Empty + } + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java index 974ae49..8215f7c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode.java +++ b/tools/layoutlib/bridge/src/android/view/View_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2010 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. @@ -14,27 +14,21 @@ * limitations under the License. */ -package android.graphics; +package android.view; -import android.graphics.PorterDuff.Mode; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -public class PorterDuffXfermode extends Xfermode { - private final Mode mMode; +/** + * Delegate used to provide new implementation of a select few methods of {@link View} + * + * Through the layoutlib_create tool, the original methods of View have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class View_Delegate { - /** - * Create an xfermode that uses the specified porter-duff mode. - * - * @param mode The porter-duff mode that is applied - */ - public PorterDuffXfermode(PorterDuff.Mode mode) { - mMode = mode; - } - - //---------- Custom Methods - - public PorterDuff.Mode getMode() { - return mMode; + @LayoutlibDelegate + /*package*/ static boolean isInEditMode(View thisView) { + return true; } - - //---------- } diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java new file mode 100644 index 0000000..bf998b8 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 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 com.android.internal.util; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + + +/** + * Delegate used to provide new implementation of a select few methods of {@link XmlUtils} + * + * Through the layoutlib_create tool, the original methods of XmlUtils have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class XmlUtils_Delegate { + + @LayoutlibDelegate + /*package*/ static final int convertValueToInt(CharSequence charSeq, int defaultValue) { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // This code is copied from the original implementation. The issue is that + // The Dalvik libraries are able to handle Integer.parse("XXXXXXXX", 16) where XXXXXXX + // is > 80000000 but the Java VM cannot. + + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) { + index++; + base = 16; + } + + return ((int)Long.parseLong(nm.substring(index), base)) * sign; + } +} 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 f91f601..2792100 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -16,68 +16,47 @@ package com.android.layoutlib.bridge; -import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.ILayoutBridge; -import com.android.layoutlib.api.ILayoutLog; -import com.android.layoutlib.api.ILayoutResult; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; -import com.android.layoutlib.api.IXmlPullParser; -import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; -import com.android.layoutlib.bridge.LayoutResult.LayoutViewInfo; -import com.android.ninepatch.NinePatch; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.Capability; +import com.android.ide.common.rendering.api.DrawableParams; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.BridgeAssetManager; +import com.android.layoutlib.bridge.impl.FontLoader; +import com.android.layoutlib.bridge.impl.RenderDrawable; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.ninepatch.NinePatchChunk; +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 android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.Region; import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; +import android.graphics.Typeface_Delegate; import android.os.Looper; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.BridgeInflater; -import android.view.InputChannel; -import android.view.IWindow; -import android.view.IWindowSession; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.AttachInfo; -import android.view.View.MeasureSpec; -import android.view.WindowManager.LayoutParams; -import android.widget.FrameLayout; -import android.widget.TabHost; -import android.widget.TabWidget; +import java.io.File; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.Collection; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; /** * Main entry point of the LayoutLib Bridge. * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. + * {@link #createScene(SceneParams)} */ -public final class Bridge implements ILayoutBridge { - - private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; - private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; +public final class Bridge extends com.android.ide.common.rendering.api.Bridge { public static class StaticMethodNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -88,84 +67,137 @@ public final class Bridge implements ILayoutBridge { } /** - * Maps from id to resource name/type. This is for android.R only. + * Lock to ensure only one rendering/inflating happens at a time. + * This is due to some singleton in the Android framework. */ - private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); + private final static ReentrantLock sLock = new ReentrantLock(); + + /** + * Maps from id to resource type/name. This is for android.R only. + */ + private final static Map<Integer, Pair<ResourceType, String>> sRMap = + new HashMap<Integer, Pair<ResourceType, String>>(); + /** * Same as sRMap except for int[] instead of int resources. This is for android.R only. */ - private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>(); + private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(); /** * Reverse map compared to sRMap, resource type -> (resource name -> id). * This is for android.R only. */ - private final static Map<String, Map<String, Integer>> sRFullMap = - new HashMap<String, Map<String,Integer>>(); + private final static Map<ResourceType, Map<String, Integer>> sRFullMap = + new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class); private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); - private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = - new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); + private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = + new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>(); private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<String, SoftReference<Bitmap>>(); - private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = - new HashMap<String, SoftReference<NinePatch>>(); + private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = + new HashMap<String, SoftReference<NinePatchChunk>>(); private static Map<String, Map<String, Integer>> sEnumValueMap; + private static Map<String, String> sPlatformProperties; /** - * A default logger than prints to stdout/stderr. + * int[] wrapper to use as keys in maps. */ - private final static ILayoutLog sDefaultLogger = new ILayoutLog() { - public void error(String message) { - System.err.println(message); + private final static class IntArray { + private int[] mArray; + + private IntArray() { + // do nothing } - public void error(Throwable t) { - String message = t.getMessage(); - if (message == null) { - message = t.getClass().getName(); - } + private IntArray(int[] a) { + mArray = a; + } + + private void set(int[] a) { + mArray = a; + } + + @Override + public int hashCode() { + return Arrays.hashCode(mArray); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + IntArray other = (IntArray) obj; + if (!Arrays.equals(mArray, other.mArray)) return false; + return true; + } + } + /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */ + private final static IntArray sIntArrayWrapper = new IntArray(); + + /** + * A default log than prints to stdout/stderr. + */ + private final static LayoutLog sDefaultLog = new LayoutLog() { + @Override + public void error(String tag, String message, Object data) { System.err.println(message); } - public void warning(String message) { + @Override + public void error(String tag, String message, Throwable throwable, Object data) { + System.err.println(message); + } + + @Override + public void warning(String tag, String message, Object data) { System.out.println(message); } }; /** - * Logger defined during a compute layout operation. - * <p/> - * This logger is generally set to {@link #sDefaultLogger} except during rendering - * operations when it might be set to a specific provided logger. - * <p/> - * To change this value, use a block synchronized on {@link #sDefaultLogger}. + * Current log. */ - private static ILayoutLog sLogger = sDefaultLogger; + private static LayoutLog sCurrentLog = sDefaultLog; - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#getApiLevel() - */ + private EnumSet<Capability> mCapabilities; + + @Override public int getApiLevel() { - return API_CURRENT; + return com.android.ide.common.rendering.api.Bridge.API_CURRENT; } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map) - */ - public boolean init( - String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) { - - return sinit(fontOsLocation, enumValueMap); + @Override + public EnumSet<Capability> getCapabilities() { + return mCapabilities; } - private static synchronized boolean sinit(String fontOsLocation, - Map<String, Map<String, Integer>> enumValueMap) { + @Override + public boolean init(Map<String,String> platformProperties, + File fontLocation, + Map<String, Map<String, Integer>> enumValueMap, + LayoutLog log) { + sPlatformProperties = platformProperties; + sEnumValueMap = enumValueMap; + + // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version + // of layoutlib_api. It is provided by the client which could have a more recent version + // with newer, unsupported capabilities. + mCapabilities = EnumSet.of( + Capability.UNBOUND_RENDERING, + Capability.CUSTOM_BACKGROUND_COLOR, + Capability.RENDER, + Capability.LAYOUT_ONLY, + Capability.EMBEDDED_LAYOUT, + Capability.VIEW_MANIPULATION); + + + BridgeAssetManager.initSystem(); // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener // on static (native) methods which prints the signature on the console and @@ -182,12 +214,8 @@ public final class Bridge implements ILayoutBridge { OverrideMethod.setDefaultListener(new MethodAdapter() { @Override public void onInvokeV(String signature, boolean isNative, Object caller) { - if (sLogger != null) { - synchronized (sDefaultLogger) { - sLogger.error("Missing Stub: " + signature + - (isNative ? " (native)" : "")); - } - } + sDefaultLog.error(null, "Missing Stub: " + signature + + (isNative ? " (native)" : ""), null /*data*/); if (debug.equalsIgnoreCase("throw")) { // Throwing this exception doesn't seem that useful. It breaks @@ -200,293 +228,138 @@ public final class Bridge implements ILayoutBridge { }); } - // Override View.isInEditMode to return true. - // - // This allows custom views that are drawn in the Graphical Layout Editor to adapt their - // rendering for preview. Most important this let custom views know that they can't expect - // the rest of their activities to be alive. - OverrideMethod.setMethodListener("android.view.View#isInEditMode()Z", - new MethodAdapter() { - @Override - public int onInvokeI(String signature, boolean isNative, Object caller) { - return 1; - } - } - ); - // load the fonts. - FontLoader fontLoader = FontLoader.create(fontOsLocation); + FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); if (fontLoader != null) { - Typeface.init(fontLoader); + Typeface_Delegate.init(fontLoader); } else { return false; } - sEnumValueMap = enumValueMap; - // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. try { - // WARNING: this only works because the class is already loaded, and therefore - // the objects returned by Field.get() are the same as the ones used by - // the code accessing the R class. - // int[] does not implement equals/hashCode, and if the parsing used a different class - // loader for the R class, this would NOT work. Class<?> r = com.android.internal.R.class; for (Class<?> inner : r.getDeclaredClasses()) { - String resType = inner.getSimpleName(); - - Map<String, Integer> fullMap = new HashMap<String, Integer>(); - sRFullMap.put(resType, fullMap); - - for (Field f : inner.getDeclaredFields()) { - // only process static final fields. Since the final attribute may have - // been altered by layoutlib_create, we only check static - int modifiers = f.getModifiers(); - if (Modifier.isStatic(modifiers)) { - Class<?> type = f.getType(); - if (type.isArray() && type.getComponentType() == int.class) { - // if the object is an int[] we put it in sRArrayMap - sRArrayMap.put((int[]) f.get(null), f.getName()); - } else if (type == int.class) { - Integer value = (Integer) f.get(null); - sRMap.put(value, new String[] { f.getName(), resType }); - fullMap.put(f.getName(), value); - } else { - assert false; + String resTypeName = inner.getSimpleName(); + ResourceType resType = ResourceType.getEnum(resTypeName); + if (resType != null) { + Map<String, Integer> fullMap = new HashMap<String, Integer>(); + sRFullMap.put(resType, fullMap); + + for (Field f : inner.getDeclaredFields()) { + // only process static final fields. Since the final attribute may have + // been altered by layoutlib_create, we only check static + int modifiers = f.getModifiers(); + if (Modifier.isStatic(modifiers)) { + Class<?> type = f.getType(); + if (type.isArray() && type.getComponentType() == int.class) { + // if the object is an int[] we put it in sRArrayMap using an IntArray + // wrapper that properly implements equals and hashcode for the array + // objects, as required by the map contract. + sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); + } else if (type == int.class) { + Integer value = (Integer) f.get(null); + sRMap.put(value, Pair.of(resType, f.getName())); + fullMap.put(f.getName(), value); + } else { + assert false; + } } } } } - } catch (IllegalArgumentException e) { - // FIXME: log/return the error (there's no logger object at this point!) - e.printStackTrace(); - return false; - } catch (IllegalAccessException e) { - e.printStackTrace(); + } catch (Throwable throwable) { + if (log != null) { + log.error(LayoutLog.TAG_BROKEN, + "Failed to load com.android.internal.R from the layout library jar", + throwable); + } return false; } return true; } - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - @Deprecated - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, - Object projectKey, - int screenWidth, int screenHeight, String themeName, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - boolean isProjectTheme = false; - if (themeName.charAt(0) == '*') { - themeName = themeName.substring(1); - isProjectTheme = true; - } - - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, - DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, - themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); - } + @Override + public boolean dispose() { + BridgeAssetManager.clearSystem(); - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - @Deprecated - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, - DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, - themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); - } + // dispose of the default typeface. + Typeface.sDefaults = null; - /* - * For compatilibty purposes, we implement the old deprecated version of computeLayout. - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) - */ - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, int density, float xdpi, float ydpi, - String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, false /* renderFullSize */, - density, xdpi, ydpi, themeName, isProjectTheme, - projectResources, frameworkResources, customViewLoader, logger); + return true; } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, boolean, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) + /** + * Starts a layout session by inflating and rendering it. The method returns a + * {@link RenderSession} on which further actions can be taken. + * + * @param params the {@link SessionParams} object with all the information necessary to create + * the scene. + * @return a new {@link RenderSession} object that contains the result of the layout. + * @since 5 */ - public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, - int screenWidth, int screenHeight, boolean renderFullSize, - int density, float xdpi, float ydpi, - String themeName, boolean isProjectTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - IProjectCallback customViewLoader, ILayoutLog logger) { - if (logger == null) { - logger = sDefaultLogger; - } - - synchronized (sDefaultLogger) { - sLogger = logger; - } - - // find the current theme and compute the style inheritance map - Map<IStyleResourceValue, IStyleResourceValue> styleParentMap = - new HashMap<IStyleResourceValue, IStyleResourceValue>(); - - IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme, - projectResources.get(BridgeConstants.RES_STYLE), - frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap); - - BridgeContext context = null; + @Override + public RenderSession createSession(SessionParams params) { try { - // setup the display Metrics. - DisplayMetrics metrics = new DisplayMetrics(); - metrics.densityDpi = density; - metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT; - metrics.scaledDensity = metrics.density; - metrics.widthPixels = screenWidth; - metrics.heightPixels = screenHeight; - metrics.xdpi = xdpi; - metrics.ydpi = ydpi; - - context = new BridgeContext(projectKey, metrics, currentTheme, projectResources, - frameworkResources, styleParentMap, customViewLoader, logger); - BridgeInflater inflater = new BridgeInflater(context, customViewLoader); - context.setBridgeInflater(inflater); - - IResourceValue windowBackground = null; - int screenOffset = 0; - if (currentTheme != null) { - windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); - windowBackground = context.resolveResValue(windowBackground); - - screenOffset = getScreenOffset(frameworkResources, currentTheme, context); - } - - // we need to make sure the Looper has been initialized for this thread. - // this is required for View that creates Handler objects. - if (Looper.myLooper() == null) { - Looper.prepare(); + Result lastResult = SUCCESS.createResult(); + RenderSessionImpl scene = new RenderSessionImpl(params); + try { + prepareThread(); + lastResult = scene.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = scene.inflate(); + if (lastResult.isSuccess()) { + lastResult = scene.render(true /*freshRender*/); + } + } + } finally { + scene.release(); + cleanupThread(); } - BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, - context, false /* platformResourceFlag */); - - ViewGroup root = new FrameLayout(context); - - View view = inflater.inflate(parser, root); - - // post-inflate process. For now this supports TabHost/TabWidget - postInflateProcess(view, customViewLoader); - - // set the AttachInfo on the root view. - AttachInfo info = new AttachInfo(new WindowSession(), new Window(), - new Handler(), null); - info.mHasWindowFocus = true; - info.mWindowVisibility = View.VISIBLE; - info.mInTouchMode = false; // this is so that we can display selections. - root.dispatchAttachedToWindow(info, 0); - - // get the background drawable - if (windowBackground != null) { - Drawable d = ResourceHelper.getDrawable(windowBackground, - context, true /* isFramework */); - root.setBackgroundDrawable(d); + return new BridgeRenderSession(scene, lastResult); + } catch (Throwable t) { + // get the real cause of the exception. + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t.getCause(); } + return new BridgeRenderSession(null, + ERROR_UNKNOWN.createResult(t2.getMessage(), t)); + } + } - // measure the views - int w_spec, h_spec; - - if (renderFullSize) { - // measure the full size needed by the layout. - w_spec = MeasureSpec.makeMeasureSpec(screenWidth, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size - h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size - view.measure(w_spec, h_spec); - - int neededWidth = root.getChildAt(0).getMeasuredWidth(); - if (neededWidth > screenWidth) { - screenWidth = neededWidth; - } - - int neededHeight = root.getChildAt(0).getMeasuredHeight(); - if (neededHeight > screenHeight - screenOffset) { - screenHeight = neededHeight + screenOffset; + @Override + public Result renderDrawable(DrawableParams params) { + try { + Result lastResult = SUCCESS.createResult(); + RenderDrawable action = new RenderDrawable(params); + try { + prepareThread(); + lastResult = action.init(params.getTimeout()); + if (lastResult.isSuccess()) { + lastResult = action.render(); } + } finally { + action.release(); + cleanupThread(); } - // remeasure with only the size we need - // This must always be done before the call to layout - w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); - h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, - MeasureSpec.EXACTLY); - view.measure(w_spec, h_spec); - - // now do the layout. - view.layout(0, screenOffset, screenWidth, screenHeight); - - // draw the views - Canvas canvas = new Canvas(screenWidth, screenHeight - screenOffset, logger); - - root.draw(canvas); - canvas.dispose(); - - return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context), - canvas.getImage()); - } catch (PostInflateException e) { - return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n" - + e.getMessage()); - } catch (Throwable e) { + return lastResult; + } catch (Throwable t) { // get the real cause of the exception. - Throwable t = e; - while (t.getCause() != null) { - t = t.getCause(); - } - - // log it - logger.error(t); - - // then return with an ERROR status and the message from the real exception - return new LayoutResult(ILayoutResult.ERROR, - t.getClass().getSimpleName() + ": " + t.getMessage()); - } finally { - // Make sure to remove static references, otherwise we could not unload the lib - BridgeResources.clearSystem(); - BridgeAssetManager.clearSystem(); - - // Remove the global logger - synchronized (sDefaultLogger) { - sLogger = sDefaultLogger; + Throwable t2 = t; + while (t2.getCause() != null) { + t2 = t.getCause(); } + return ERROR_UNKNOWN.createResult(t2.getMessage(), t); } } - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object) - */ + @Override public void clearCaches(Object projectKey) { if (projectKey != null) { sProjectBitmapCache.remove(projectKey); @@ -495,390 +368,104 @@ public final class Bridge implements ILayoutBridge { } /** - * Returns details of a framework resource from its integer value. - * @param value the integer value - * @return an array of 2 strings containing the resource name and type, or null if the id - * does not match any resource. + * Returns the lock for the bridge */ - public static String[] resolveResourceValue(int value) { - return sRMap.get(value); - + public static ReentrantLock getLock() { + return sLock; } /** - * Returns the name of a framework resource whose value is an int array. - * @param array + * Prepares the current thread for rendering. + * + * Note that while this can be called several time, the first call to {@link #cleanupThread()} + * will do the clean-up, and make the thread unable to do further scene actions. */ - public static String resolveResourceValue(int[] array) { - return sRArrayMap.get(array); + public static void prepareThread() { + // we need to make sure the Looper has been initialized for this thread. + // this is required for View that creates Handler objects. + if (Looper.myLooper() == null) { + Looper.prepare(); + } } /** - * Returns the integer id of a framework resource, from a given resource type and resource name. - * @param type the type of the resource - * @param name the name of the resource. - * @return an {@link Integer} containing the resource id, or null if no resource were found. + * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. + * <p> + * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single + * call to this will prevent the thread from doing further scene actions */ - public static Integer getResourceValue(String type, String name) { - Map<String, Integer> map = sRFullMap.get(type); - if (map != null) { - return map.get(name); - } - - return null; + public static void cleanupThread() { + // clean up the looper + Looper.sThreadLocal.remove(); } - static Map<String, Integer> getEnumValues(String attributeName) { - if (sEnumValueMap != null) { - return sEnumValueMap.get(attributeName); - } - - return null; + public static LayoutLog getLog() { + return sCurrentLog; } - /** - * Visits a View and its children and generate a {@link ILayoutViewInfo} containing the - * bounds of all the views. - * @param view the root View - * @param context the context. - */ - private ILayoutViewInfo visit(View view, BridgeContext context) { - if (view == null) { - return null; + public static void setLog(LayoutLog log) { + // check only the thread currently owning the lock can do this. + if (sLock.isHeldByCurrentThread() == false) { + throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); } - LayoutViewInfo result = new LayoutViewInfo(view.getClass().getName(), - context.getViewKey(view), - view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - int n = group.getChildCount(); - ILayoutViewInfo[] children = new ILayoutViewInfo[n]; - for (int i = 0; i < group.getChildCount(); i++) { - children[i] = visit(group.getChildAt(i), context); - } - result.setChildren(children); + if (log != null) { + sCurrentLog = log; + } else { + sCurrentLog = sDefaultLog; } - - return result; } /** - * Compute style information from the given list of style for the project and framework. - * @param themeName the name of the current theme. In order to differentiate project and - * platform themes sharing the same name, all project themes must be prepended with - * a '*' character. - * @param isProjectTheme Is this a project theme - * @param inProjectStyleMap the project style map - * @param inFrameworkStyleMap the framework style map - * @param outInheritanceMap the map of style inheritance. This is filled by the method - * @return the {@link IStyleResourceValue} matching <var>themeName</var> + * Returns details of a framework resource from its integer value. + * @param value the integer value + * @return a Pair containing the resource type and name, or null if the id + * does not match any resource. */ - private IStyleResourceValue computeStyleMaps( - String themeName, boolean isProjectTheme, Map<String, - IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap, - Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { - - if (inProjectStyleMap != null && inFrameworkStyleMap != null) { - // first, get the theme - IResourceValue theme = null; - - // project theme names have been prepended with a * - if (isProjectTheme) { - theme = inProjectStyleMap.get(themeName); - } else { - theme = inFrameworkStyleMap.get(themeName); - } - - if (theme instanceof IStyleResourceValue) { - // compute the inheritance map for both the project and framework styles - computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, - inFrameworkStyleMap, outInheritanceMap); - - // Compute the style inheritance for the framework styles/themes. - // Since, for those, the style parent values do not contain 'android:' - // we want to force looking in the framework style only to avoid using - // similarly named styles from the project. - // To do this, we pass null in lieu of the project style map. - computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, - inFrameworkStyleMap, outInheritanceMap); - - return (IStyleResourceValue)theme; - } - } - - return null; + public static Pair<ResourceType, String> resolveResourceId(int value) { + return sRMap.get(value); } /** - * Compute the parent style for all the styles in a given list. - * @param styles the styles for which we compute the parent. - * @param inProjectStyleMap the map of project styles. - * @param inFrameworkStyleMap the map of framework styles. - * @param outInheritanceMap the map of style inheritance. This is filled by the method. + * Returns the name of a framework resource whose value is an int array. + * @param array */ - private void computeStyleInheritance(Collection<IResourceValue> styles, - Map<String, IResourceValue> inProjectStyleMap, - Map<String, IResourceValue> inFrameworkStyleMap, - Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { - for (IResourceValue value : styles) { - if (value instanceof IStyleResourceValue) { - IStyleResourceValue style = (IStyleResourceValue)value; - IStyleResourceValue parentStyle = null; - - // first look for a specified parent. - String parentName = style.getParentStyle(); - - // no specified parent? try to infer it from the name of the style. - if (parentName == null) { - parentName = getParentName(value.getName()); - } - - if (parentName != null) { - parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); - - if (parentStyle != null) { - outInheritanceMap.put(style, parentStyle); - } - } - } - } + public static String resolveResourceId(int[] array) { + sIntArrayWrapper.set(array); + return sRArrayMap.get(sIntArrayWrapper); } /** - * Searches for and returns the {@link IStyleResourceValue} from a given name. - * <p/>The format of the name can be: - * <ul> - * <li>[android:]<name></li> - * <li>[android:]style/<name></li> - * <li>@[android:]style/<name></li> - * </ul> - * @param parentName the name of the style. - * @param inProjectStyleMap the project style map. Can be <code>null</code> - * @param inFrameworkStyleMap the framework style map. - * @return The matching {@link IStyleResourceValue} object or <code>null</code> if not found. + * Returns the integer id of a framework resource, from a given resource type and resource name. + * @param type the type of the resource + * @param name the name of the resource. + * @return an {@link Integer} containing the resource id, or null if no resource were found. */ - private IStyleResourceValue getStyle(String parentName, - Map<String, IResourceValue> inProjectStyleMap, - Map<String, IResourceValue> inFrameworkStyleMap) { - boolean frameworkOnly = false; - - String name = parentName; - - // remove the useless @ if it's there - if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { - name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); - } - - // check for framework identifier. - if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - // at this point we could have the format <type>/<name>. we want only the name as long as - // the type is style. - if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { - name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); - } else if (name.indexOf('/') != -1) { - return null; - } - - IResourceValue parent = null; - - // if allowed, search in the project resources. - if (frameworkOnly == false && inProjectStyleMap != null) { - parent = inProjectStyleMap.get(name); - } - - // if not found, then look in the framework resources. - if (parent == null) { - parent = inFrameworkStyleMap.get(name); - } - - // make sure the result is the proper class type and return it. - if (parent instanceof IStyleResourceValue) { - return (IStyleResourceValue)parent; + public static Integer getResourceId(ResourceType type, String name) { + Map<String, Integer> map = sRFullMap.get(type); + if (map != null) { + return map.get(name); } - sLogger.error(String.format("Unable to resolve parent style name: %s", parentName)); - return null; } /** - * Computes the name of the parent style, or <code>null</code> if the style is a root style. + * Returns the list of possible enums for a given attribute name. */ - private String getParentName(String styleName) { - int index = styleName.lastIndexOf('.'); - if (index != -1) { - return styleName.substring(0, index); + public static Map<String, Integer> getEnumValues(String attributeName) { + if (sEnumValueMap != null) { + return sEnumValueMap.get(attributeName); } return null; } /** - * Returns the top screen offset. This depends on whether the current theme defines the user - * of the title and status bars. - * @param frameworkResources The framework resources - * @param currentTheme The current theme - * @param context The context - * @return the pixel height offset - */ - private int getScreenOffset(Map<String, Map<String, IResourceValue>> frameworkResources, - IStyleResourceValue currentTheme, BridgeContext context) { - int offset = 0; - - // get the title bar flag from the current theme. - IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - // if there's a value and it's true (default is false) - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - // default size of the window title bar - int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT; - - // get value from the theme. - value = context.findItemInStyle(currentTheme, "windowTitleSize"); - - // resolve it - value = context.resolveResValue(value); - - if (value != null) { - // get the numerical value, if available - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - offset += defaultOffset; - } - - // get the fullscreen flag from the current theme. - value = context.findItemInStyle(currentTheme, "windowFullscreen"); - - // because it may reference something else, we resolve it. - value = context.resolveResValue(value); - - if (value == null || value.getValue() == null || - XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { - - // default value - int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT; - - // get the real value, first the list of Dimensions from the framework map - Map<String, IResourceValue> dimens = frameworkResources.get(BridgeConstants.RES_DIMEN); - - // now get the value - value = dimens.get("status_bar_height"); - if (value != null) { - TypedValue typedValue = ResourceHelper.getValue(value.getValue()); - if (typedValue != null) { - // compute the pixel value based on the display metrics - defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); - } - } - - // add the computed offset. - offset += defaultOffset; - } - - return offset; - } - - /** - * Post process on a view hierachy that was just inflated. - * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the - * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically - * based on the content of the {@link FrameLayout}. - * @param view the root view to process. - * @param projectCallback callback to the project. + * Returns the platform build properties. */ - private void postInflateProcess(View view, IProjectCallback projectCallback) - throws PostInflateException { - if (view instanceof TabHost) { - setupTabHost((TabHost)view, projectCallback); - } else if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup)view; - final int count = group.getChildCount(); - for (int c = 0 ; c < count ; c++) { - View child = group.getChildAt(c); - postInflateProcess(child, projectCallback); - } - } - } - - /** - * Sets up a {@link TabHost} object. - * @param tabHost the TabHost to setup. - * @param projectCallback The project callback object to access the project R class. - * @throws PostInflateException - */ - private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) - throws PostInflateException { - // look for the TabWidget, and the FrameLayout. They have their own specific names - View v = tabHost.findViewById(android.R.id.tabs); - - if (v == null) { - throw new PostInflateException( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); - } - - if ((v instanceof TabWidget) == false) { - throw new PostInflateException(String.format( - "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + - "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); - } - - v = tabHost.findViewById(android.R.id.tabcontent); - - if (v == null) { - // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) - throw new PostInflateException( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); - } - - if ((v instanceof FrameLayout) == false) { - throw new PostInflateException(String.format( - "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + - "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); - } - - FrameLayout content = (FrameLayout)v; - - // now process the content of the framelayout and dynamically create tabs for it. - final int count = content.getChildCount(); - - if (count == 0) { - throw new PostInflateException( - "The FrameLayout for the TabHost has no content. Rendering failed.\n"); - } - - // this must be called before addTab() so that the TabHost searches its TabWidget - // and FrameLayout. - tabHost.setup(); - - // for each child of the framelayout, add a new TabSpec - for (int i = 0 ; i < count ; i++) { - View child = content.getChildAt(i); - String tabSpec = String.format("tab_spec%d", i+1); - int id = child.getId(); - String[] resource = projectCallback.resolveResourceValue(id); - String name; - if (resource != null) { - name = resource[0]; // 0 is resource name, 1 is resource type. - } else { - name = String.format("Tab %d", i+1); // default name if id is unresolved. - } - tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); - } + public static Map<String, String> getPlatformProperties() { + return sPlatformProperties; } /** @@ -888,7 +475,7 @@ public final class Bridge implements ILayoutBridge { * @param projectKey the key of the project, or null to query the framework cache. * @return the cached Bitmap or null if not found. */ - static Bitmap getCachedBitmap(String value, Object projectKey) { + public static Bitmap getCachedBitmap(String value, Object projectKey) { if (projectKey != null) { Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); if (map != null) { @@ -913,7 +500,7 @@ public final class Bridge implements ILayoutBridge { * @param bmp the Bitmap object * @param projectKey the key of the project, or null to put the bitmap in the framework cache. */ - static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { + public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { if (projectKey != null) { Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); @@ -929,24 +516,24 @@ public final class Bridge implements ILayoutBridge { } /** - * Returns the 9 patch for a specific path, from a specific project cache, or from the + * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the * framework cache. * @param value the path of the 9 patch * @param projectKey the key of the project, or null to query the framework cache. * @return the cached 9 patch or null if not found. */ - static NinePatch getCached9Patch(String value, Object projectKey) { + public static NinePatchChunk getCached9Patch(String value, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map != null) { - SoftReference<NinePatch> ref = map.get(value); + SoftReference<NinePatchChunk> ref = map.get(value); if (ref != null) { return ref.get(); } } } else { - SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); + SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); if (ref != null) { return ref.get(); } @@ -956,224 +543,25 @@ public final class Bridge implements ILayoutBridge { } /** - * Sets a 9 patch in a project cache or in the framework cache. + * Sets a 9 patch chunk in a project cache or in the framework cache. * @param value the path of the 9 patch * @param ninePatch the 9 patch object * @param projectKey the key of the project, or null to put the bitmap in the framework cache. */ - static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { + public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map == null) { - map = new HashMap<String, SoftReference<NinePatch>>(); + map = new HashMap<String, SoftReference<NinePatchChunk>>(); sProject9PatchCache.put(projectKey, map); } - map.put(value, new SoftReference<NinePatch>(ninePatch)); + map.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } else { - sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); + sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } } - private static final class PostInflateException extends Exception { - private static final long serialVersionUID = 1L; - - public PostInflateException(String message) { - super(message); - } - } - - /** - * Implementation of {@link IWindowSession} so that mSession is not null in - * the {@link SurfaceView}. - */ - private static final class WindowSession implements IWindowSession { - - @SuppressWarnings("unused") - public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3, - InputChannel outInputchannel) - throws RemoteException { - // pass for now. - return 0; - } - - @SuppressWarnings("unused") - public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) - throws RemoteException { - // pass for now. - return 0; - } - - @SuppressWarnings("unused") - public void finishDrawing(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void finishKey(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public boolean getInTouchMode() throws RemoteException { - // pass for now. - return false; - } - - @SuppressWarnings("unused") - public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { - // pass for now. - return false; - } - - @SuppressWarnings("unused") - public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4, - boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, Surface arg8) - throws RemoteException { - // pass for now. - return 0; - } - - public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { - // pass for now. - } - - @SuppressWarnings("unused") - public void remove(IWindow arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setInTouchMode(boolean arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void setInsets(IWindow window, int touchable, Rect contentInsets, - Rect visibleInsets) { - // pass for now. - } - - @SuppressWarnings("unused") - public void setWallpaperPosition(IBinder window, float x, float y, - float xStep, float yStep) { - // pass for now. - } - - @SuppressWarnings("unused") - public void wallpaperOffsetsComplete(IBinder window) { - // pass for now. - } - - @SuppressWarnings("unused") - public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, - int z, Bundle extras, boolean sync) { - // pass for now. - return null; - } - - @SuppressWarnings("unused") - public void wallpaperCommandComplete(IBinder window, Bundle result) { - // pass for now. - } - - @SuppressWarnings("unused") - public void closeSystemDialogs(String reason) { - // pass for now. - } - - public IBinder asBinder() { - // pass for now. - return null; - } - } - - /** - * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. - */ - private static final class Window implements IWindow { - - @SuppressWarnings("unused") - public void dispatchAppVisibility(boolean arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchGetNewSurface() throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchKey(KeyEvent arg0) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) - throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5) - throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, - boolean sync) { - // pass for now. - } - - @SuppressWarnings("unused") - public void dispatchWallpaperCommand(String action, int x, int y, - int z, Bundle extras, boolean sync) { - // pass for now. - } - - @SuppressWarnings("unused") - public void closeSystemDialogs(String reason) { - // pass for now. - } - - public IBinder asBinder() { - // pass for now. - return null; - } - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java index 791e53b..112af1e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java @@ -41,24 +41,6 @@ public class BridgeConstants { public final static String R = "com.android.internal.R"; - public final static String PREFIX_ANDROID_RESOURCE_REF = "@android:"; - public final static String PREFIX_RESOURCE_REF = "@"; - public final static String PREFIX_ANDROID_THEME_REF = "?android:"; - public final static String PREFIX_THEME_REF = "?"; - - public final static String PREFIX_ANDROID = "android:"; - - public final static String RES_STYLE = "style"; - public final static String RES_ATTR = "attr"; - public final static String RES_DIMEN = "dimen"; - public final static String RES_DRAWABLE = "drawable"; - public final static String RES_COLOR = "color"; - public final static String RES_LAYOUT = "layout"; - public final static String RES_STRING = "string"; - public final static String RES_ID = "id"; - - public final static String REFERENCE_STYLE = RES_STYLE + "/"; - public final static String REFERENCE_NULL = "@null"; public final static String MATCH_PARENT = "match_parent"; public final static String FILL_PARENT = "fill_parent"; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java deleted file mode 100644 index 20ccc0b..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContentResolver.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.layoutlib.bridge; - -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.IContentProvider; -import android.content.OperationApplicationException; -import android.content.res.AssetFileDescriptor; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; - -import java.io.FileNotFoundException; -import java.util.ArrayList; - -/** - * A mock content resolver for the LayoutLib Bridge. - * <p/> - * It won't serve any actual data but it's good enough for all - * the widgets which expect to have a content resolver available via - * {@link BridgeContext#getContentResolver()}. - */ -public class BridgeContentResolver extends ContentResolver { - - private BridgeContentProvider mProvider = null; - - public static final class BridgeContentProvider implements IContentProvider { - - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> arg0) - throws RemoteException, OperationApplicationException { - // TODO Auto-generated method stub - return null; - } - - public int bulkInsert(Uri arg0, ContentValues[] arg1) throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public IBulkCursor bulkQuery(Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4, IContentObserver arg5, CursorWindow arg6) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public Bundle call(String arg0, String arg1, Bundle arg2) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public int delete(Uri arg0, String arg1, String[] arg2) throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public String getType(Uri arg0) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public Uri insert(Uri arg0, ContentValues arg1) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public AssetFileDescriptor openAssetFile(Uri arg0, String arg1) throws RemoteException, - FileNotFoundException { - // TODO Auto-generated method stub - return null; - } - - public ParcelFileDescriptor openFile(Uri arg0, String arg1) throws RemoteException, - FileNotFoundException { - // TODO Auto-generated method stub - return null; - } - - public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) - throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) - throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public IBinder asBinder() { - // TODO Auto-generated method stub - return null; - } - - } - - public BridgeContentResolver(Context context) { - super(context); - } - - @Override - public IContentProvider acquireProvider(Context c, String name) { - if (mProvider == null) { - mProvider = new BridgeContentProvider(); - } - - return mProvider; - } - - @Override - public IContentProvider acquireExistingProvider(Context c, String name) { - if (mProvider == null) { - mProvider = new BridgeContentProvider(); - } - - return mProvider; - } - - @Override - public boolean releaseProvider(IContentProvider icp) { - // ignore - return false; - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void registerContentObserver(Uri uri, boolean notifyForDescendents, - ContentObserver observer) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void unregisterContentObserver(ContentObserver observer) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void startSync(Uri uri, Bundle extras) { - // pass - } - - /** - * Stub for the layoutlib bridge content resolver. - */ - @Override - public void cancelSync(Uri uri) { - // pass - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java new file mode 100644 index 0000000..bf22c4d --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.Map; + +/** + * An implementation of {@link RenderSession}. + * + * This is a pretty basic class that does almost nothing. All of the work is done in + * {@link RenderSessionImpl}. + * + */ +public class BridgeRenderSession extends RenderSession { + + private final RenderSessionImpl mSession; + private Result mLastResult; + + @Override + public Result getResult() { + return mLastResult; + } + + @Override + public BufferedImage getImage() { + return mSession.getImage(); + } + + @Override + public boolean isAlphaChannelImage() { + return mSession.isAlphaChannelImage(); + } + + @Override + public List<ViewInfo> getRootViews() { + return mSession.getViewInfos(); + } + + @Override + public Map<String, String> getDefaultProperties(Object viewObject) { + return mSession.getDefaultProperties(viewObject); + } + + @Override + public Result getProperty(Object objectView, String propertyName) { + // TODO Auto-generated method stub + return super.getProperty(objectView, propertyName); + } + + @Override + public Result setProperty(Object objectView, String propertyName, String propertyValue) { + // TODO Auto-generated method stub + return super.setProperty(objectView, propertyName, propertyValue); + } + + @Override + public Result getViewParent(Object viewObject) { + if (viewObject instanceof View) { + return Status.SUCCESS.createResult(((View)viewObject).getParent()); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result getViewIndex(Object viewObject) { + if (viewObject instanceof View) { + View view = (View) viewObject; + ViewParent parentView = view.getParent(); + + if (parentView instanceof ViewGroup) { + Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); + } + + return Status.SUCCESS.createResult(); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result render(long timeout) { + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(timeout); + if (mLastResult.isSuccess()) { + mLastResult = mSession.render(false /*freshRender*/); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public Result animate(Object targetObject, String animationName, + boolean isFrameworkAnimation, IAnimationListener listener) { + // Animation is only supported in API 11+ + return super.animate(targetObject, animationName, isFrameworkAnimation, listener); + } + + @Override + public Result insertChild(Object parentView, ILayoutPullParser childXml, int index, + IAnimationListener listener) { + if (parentView instanceof ViewGroup == false) { + throw new IllegalArgumentException("parentView is not a ViewGroup"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index, + listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + + @Override + public Result moveChild(Object parentView, Object childView, int index, + Map<String, String> layoutParams, IAnimationListener listener) { + if (parentView instanceof ViewGroup == false) { + throw new IllegalArgumentException("parentView is not a ViewGroup"); + } + if (childView instanceof View == false) { + throw new IllegalArgumentException("childView is not a View"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.moveChild((ViewGroup) parentView, (View) childView, index, + layoutParams, listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public Result removeChild(Object childView, IAnimationListener listener) { + if (childView instanceof View == false) { + throw new IllegalArgumentException("childView is not a View"); + } + + try { + Bridge.prepareThread(); + mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT); + if (mLastResult.isSuccess()) { + mLastResult = mSession.removeChild((View) childView, listener); + } + } finally { + mSession.release(); + Bridge.cleanupThread(); + } + + return mLastResult; + } + + @Override + public void dispose() { + } + + /*package*/ BridgeRenderSession(RenderSessionImpl scene, Result lastResult) { + mSession = scene; + if (scene != null) { + mSession.setScene(this); + } + mLastResult = lastResult; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java deleted file mode 100644 index c4c5225..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/LayoutResult.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.bridge; - -import com.android.layoutlib.api.ILayoutResult; - -import java.awt.image.BufferedImage; - -/** - * Implementation of {@link ILayoutResult} - */ -public final class LayoutResult implements ILayoutResult { - - private final ILayoutViewInfo mRootView; - private final BufferedImage mImage; - private final int mSuccess; - private final String mErrorMessage; - - /** - * Creates a {@link #SUCCESS} {@link ILayoutResult} with the specified params - * @param rootView - * @param image - */ - public LayoutResult(ILayoutViewInfo rootView, BufferedImage image) { - mSuccess = SUCCESS; - mErrorMessage = null; - mRootView = rootView; - mImage = image; - } - - /** - * Creates a LayoutResult with a specific success code and associated message - * @param code - * @param message - */ - public LayoutResult(int code, String message) { - mSuccess = code; - mErrorMessage = message; - mRootView = null; - mImage = null; - } - - public int getSuccess() { - return mSuccess; - } - - public String getErrorMessage() { - return mErrorMessage; - } - - public BufferedImage getImage() { - return mImage; - } - - public ILayoutViewInfo getRootView() { - return mRootView; - } - - /** - * Implementation of {@link ILayoutResult.ILayoutViewInfo} - */ - public static final class LayoutViewInfo implements ILayoutViewInfo { - private final Object mKey; - private final String mName; - private final int mLeft; - private final int mRight; - private final int mTop; - private final int mBottom; - private ILayoutViewInfo[] mChildren; - - public LayoutViewInfo(String name, Object key, int left, int top, int right, int bottom) { - mName = name; - mKey = key; - mLeft = left; - mRight = right; - mTop = top; - mBottom = bottom; - } - - public void setChildren(ILayoutViewInfo[] children) { - mChildren = children; - } - - public ILayoutViewInfo[] getChildren() { - return mChildren; - } - - public Object getViewKey() { - return mKey; - } - - public String getName() { - return mName; - } - - public int getLeft() { - return mLeft; - } - - public int getTop() { - return mTop; - } - - public int getRight() { - return mRight; - } - - public int getBottom() { - return mBottom; - } - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java deleted file mode 100644 index abbf2f0..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/NinePatchDrawable.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.bridge; - -import com.android.ninepatch.NinePatch; - -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -public class NinePatchDrawable extends Drawable { - - private NinePatch m9Patch; - - NinePatchDrawable(NinePatch ninePatch) { - m9Patch = ninePatch; - } - - @Override - public int getMinimumWidth() { - return m9Patch.getWidth(); - } - - @Override - public int getMinimumHeight() { - return m9Patch.getHeight(); - } - - /** - * Return the intrinsic width of the underlying drawable object. Returns - * -1 if it has no intrinsic width, such as with a solid color. - */ - @Override - public int getIntrinsicWidth() { - return m9Patch.getWidth(); - } - - /** - * Return the intrinsic height of the underlying drawable object. Returns - * -1 if it has no intrinsic height, such as with a solid color. - */ - @Override - public int getIntrinsicHeight() { - return m9Patch.getHeight(); - } - - /** - * Return in padding the insets suggested by this Drawable for placing - * content inside the drawable's bounds. Positive values move toward the - * center of the Drawable (set Rect.inset). Returns true if this drawable - * actually has a padding, else false. When false is returned, the padding - * is always set to 0. - */ - @Override - public boolean getPadding(Rect padding) { - int[] padd = new int[4]; - m9Patch.getPadding(padd); - padding.left = padd[0]; - padding.top = padd[1]; - padding.right = padd[2]; - padding.bottom = padd[3]; - return true; - } - - @Override - public void draw(Canvas canvas) { - Rect r = getBounds(); - m9Patch.draw(canvas.getGraphics2d(), r.left, r.top, r.width(), r.height()); - - return; - } - - - // ----------- Not implemented methods --------------- - - - @Override - public int getOpacity() { - // FIXME - return 0xFF; - } - - @Override - public void setAlpha(int arg0) { - // FIXME ! - } - - @Override - public void setColorFilter(ColorFilter arg0) { - // FIXME - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java deleted file mode 100644 index 01a4871..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceValue.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IResourceValue; - -/** - * Basic implementation of IResourceValue. - */ -class ResourceValue implements IResourceValue { - private final String mType; - private final String mName; - private String mValue = null; - - ResourceValue(String name) { - mType = null; - mName = name; - } - - public ResourceValue(String type, String name, String value) { - mType = type; - mName = name; - mValue = value; - } - - public String getType() { - return mType; - } - - public final String getName() { - return mName; - } - - public final String getValue() { - return mValue; - } - - public final void setValue(String value) { - mValue = value; - } - - public void replaceWith(ResourceValue value) { - mValue = value.mValue; - } - - public boolean isFramework() { - // ResourceValue object created directly in the framework are used to describe - // non resolvable coming from the XML. Since they will never be cached (as they can't - // be a value pointing to a bitmap, or they'd be resolvable.), the return value deoes - // not matter. - return false; - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeAssetManager.java index 71803fc..a825060 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeAssetManager.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.Bridge; import android.content.res.AssetManager; @@ -28,7 +30,7 @@ public class BridgeAssetManager extends AssetManager { * <p/> * {@link Bridge} calls this method after setting up a new bridge. */ - /*package*/ static AssetManager initSystem() { + /*package*/ public static AssetManager initSystem() { if (!(AssetManager.sSystem instanceof BridgeAssetManager)) { // Note that AssetManager() creates a system AssetManager and we override it // with our BridgeAssetManager. @@ -42,7 +44,7 @@ public class BridgeAssetManager extends AssetManager { * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects * around that would prevent us from unloading the library. */ - /*package*/ static void clearSystem() { + public static void clearSystem() { AssetManager.sSystem = null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java new file mode 100644 index 0000000..3835378 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.android; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.IContentProvider; +import android.content.OperationApplicationException; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.CursorWindow; +import android.database.IBulkCursor; +import android.database.IContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Mock implementation of {@link IContentProvider}. + * + * TODO: never return null when the method is not supposed to. Return fake data instead. + */ +public final class BridgeContentProvider implements IContentProvider { + + public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> arg0) + throws RemoteException, OperationApplicationException { + // TODO Auto-generated method stub + return null; + } + + public int bulkInsert(Uri arg0, ContentValues[] arg1) throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public IBulkCursor bulkQuery(Uri arg0, String[] arg1, String arg2, String[] arg3, + String arg4, IContentObserver arg5, CursorWindow arg6) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public Bundle call(String arg0, String arg1, Bundle arg2) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public int delete(Uri arg0, String arg1, String[] arg2) throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public String getType(Uri arg0) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public Uri insert(Uri arg0, ContentValues arg1) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public AssetFileDescriptor openAssetFile(Uri arg0, String arg1) throws RemoteException, + FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + + public ParcelFileDescriptor openFile(Uri arg0, String arg1) throws RemoteException, + FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + + public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) + throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) + throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + public IBinder asBinder() { + // TODO Auto-generated method stub + return null; + } + + public String[] getStreamTypes(Uri arg0, String arg1) throws RemoteException { + // TODO Auto-generated method stub + return null; + } + + public AssetFileDescriptor openTypedAssetFile(Uri arg0, String arg1, Bundle arg2) + throws RemoteException, FileNotFoundException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java new file mode 100644 index 0000000..0257686 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2009 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 com.android.layoutlib.bridge.android; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentProvider; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +/** + * A mock content resolver for the LayoutLib Bridge. + * <p/> + * It won't serve any actual data but it's good enough for all + * the widgets which expect to have a content resolver available via + * {@link BridgeContext#getContentResolver()}. + */ +public class BridgeContentResolver extends ContentResolver { + + private BridgeContentProvider mProvider = null; + + public BridgeContentResolver(Context context) { + super(context); + } + + @Override + public IContentProvider acquireProvider(Context c, String name) { + if (mProvider == null) { + mProvider = new BridgeContentProvider(); + } + + return mProvider; + } + + @Override + public IContentProvider acquireExistingProvider(Context c, String name) { + if (mProvider == null) { + mProvider = new BridgeContentProvider(); + } + + return mProvider; + } + + @Override + public boolean releaseProvider(IContentProvider icp) { + // ignore + return false; + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void unregisterContentObserver(ContentObserver observer) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void startSync(Uri uri, Bundle extras) { + // pass + } + + /** + * Stub for the layoutlib bridge content resolver. + */ + @Override + public void cancelSync(Uri uri) { + // pass + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index ecd22e2..c4eee17 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -14,13 +14,20 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.ILayoutLog; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; - +package com.android.layoutlib.bridge.android; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.Stack; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -47,7 +54,8 @@ import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.view.BridgeInflater; +import android.util.TypedValue; +import android.view.LayoutInflater; import android.view.View; import java.io.File; @@ -57,81 +65,98 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; /** - * Custom implementation of Context to handle non compiled resources. + * Custom implementation of Context/Activity to handle non compiled resources. */ -public final class BridgeContext extends Context { +public final class BridgeContext extends Activity { - private final Resources mResources; - private final Theme mTheme; + private Resources mSystemResources; private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>(); - private final IStyleResourceValue mThemeValues; private final Object mProjectKey; - private final Map<String, Map<String, IResourceValue>> mProjectResources; - private final Map<String, Map<String, IResourceValue>> mFrameworkResources; - private final Map<IStyleResourceValue, IStyleResourceValue> mStyleInheritanceMap; + private final DisplayMetrics mMetrics; + private final RenderResources mRenderResources; + private final ApplicationInfo mApplicationInfo; + + private final Map<Object, Map<String, String>> mDefaultPropMaps = + new IdentityHashMap<Object, Map<String,String>>(); - // maps for dynamically generated id representing style objects (IStyleResourceValue) - private Map<Integer, IStyleResourceValue> mDynamicIdToStyleMap; - private Map<IStyleResourceValue, Integer> mStyleToDynamicIdMap; + // maps for dynamically generated id representing style objects (StyleResourceValue) + private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap; + private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap; private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style // cache for TypedArray generated from IStyleResourceValue object private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache; - private BridgeInflater mInflater; + private BridgeInflater mBridgeInflater; private final IProjectCallback mProjectCallback; - private final ILayoutLog mLogger; private BridgeContentResolver mContentResolver; + private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>(); + /** * @param projectKey An Object identifying the project. This is used for the cache mechanism. * @param metrics the {@link DisplayMetrics}. * @param themeName The name of the theme to use. * @param projectResources the resources of the project. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the - * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * map contains (String, {@link }) pairs where the key is the resource name, * and the value is the resource value. * @param frameworkResources the framework resources. The map contains (String, map) pairs * where the string is the type of the resource reference used in the layout file, and the map - * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * contains (String, {@link ResourceValue}) pairs where the key is the resource name, and the * value is the resource value. * @param styleInheritanceMap - * @param customViewLoader + * @param projectCallback + * @param targetSdkVersion the targetSdkVersion of the application. */ public BridgeContext(Object projectKey, DisplayMetrics metrics, - IStyleResourceValue currentTheme, - Map<String, Map<String, IResourceValue>> projectResources, - Map<String, Map<String, IResourceValue>> frameworkResources, - Map<IStyleResourceValue, IStyleResourceValue> styleInheritanceMap, - IProjectCallback customViewLoader, ILayoutLog logger) { + RenderResources renderResources, + IProjectCallback projectCallback, + int targetSdkVersion) { mProjectKey = projectKey; - mProjectCallback = customViewLoader; - mLogger = logger; + mMetrics = metrics; + mProjectCallback = projectCallback; + + mRenderResources = renderResources; + + mApplicationInfo = new ApplicationInfo(); + mApplicationInfo.targetSdkVersion = targetSdkVersion; + } + + /** + * Initializes the {@link Resources} singleton to be linked to this {@link Context}, its + * {@link DisplayMetrics}, {@link Configuration}, and {@link IProjectCallback}. + * + * @see #disposeResources() + */ + public void initResources() { + AssetManager assetManager = AssetManager.getSystem(); Configuration config = new Configuration(); - AssetManager assetManager = BridgeAssetManager.initSystem(); - mResources = BridgeResources.initSystem( + mSystemResources = BridgeResources.initSystem( this, assetManager, - metrics, + mMetrics, config, - customViewLoader); - - mTheme = mResources.newTheme(); + mProjectCallback); + mTheme = mSystemResources.newTheme(); + } - mThemeValues = currentTheme; - mProjectResources = projectResources; - mFrameworkResources = frameworkResources; - mStyleInheritanceMap = styleInheritanceMap; + /** + * Disposes the {@link Resources} singleton. + */ + public void disposeResources() { + BridgeResources.disposeSystem(); } public void setBridgeInflater(BridgeInflater inflater) { - mInflater = inflater; + mBridgeInflater = inflater; } public void addViewKey(View view, Object viewKey) { @@ -146,19 +171,112 @@ public final class BridgeContext extends Context { return mProjectKey; } + public DisplayMetrics getMetrics() { + return mMetrics; + } + public IProjectCallback getProjectCallback() { return mProjectCallback; } - public ILayoutLog getLogger() { - return mLogger; + public RenderResources getRenderResources() { + return mRenderResources; + } + + public Map<String, String> getDefaultPropMap(Object key) { + return mDefaultPropMaps.get(key); + } + + /** + * Adds a parser to the stack. + * @param parser the parser to add. + */ + public void pushParser(BridgeXmlBlockParser parser) { + mParserStack.push(parser); + } + + /** + * Removes the parser at the top of the stack + */ + public void popParser() { + mParserStack.pop(); + } + + /** + * Returns the current parser at the top the of the stack. + * @return a parser or null. + */ + public BridgeXmlBlockParser getCurrentParser() { + return mParserStack.peek(); + } + + /** + * Returns the previous parser. + * @return a parser or null if there isn't any previous parser + */ + public BridgeXmlBlockParser getPreviousParser() { + if (mParserStack.size() < 2) { + return null; + } + return mParserStack.get(mParserStack.size() - 2); + } + + public boolean resolveThemeAttribute(int resid, TypedValue outValue, boolean resolveRefs) { + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid); + if (resourceInfo == null) { + resourceInfo = mProjectCallback.resolveResourceId(resid); + } + + if (resourceInfo == null) { + return false; + } + + ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond()); + if (resolveRefs) { + value = mRenderResources.resolveResValue(value); + } + + // check if this is a style resource + if (value instanceof StyleResourceValue) { + // get the id that will represent this style. + outValue.resourceId = getDynamicIdByStyle((StyleResourceValue)value); + return true; + } + + + int a; + // if this is a framework value. + if (value.isFramework()) { + // look for idName in the android R classes. + // use 0 a default res value as it's not a valid id value. + a = getFrameworkResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/); + } else { + // look for idName in the project R class. + // use 0 a default res value as it's not a valid id value. + a = getProjectResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/); + } + + if (a != 0) { + outValue.resourceId = a; + return true; + } + + return false; + } + + + // ------------- Activity Methods + + @Override + public LayoutInflater getLayoutInflater() { + return mBridgeInflater; } // ------------ Context methods @Override public Resources getResources() { - return mResources; + return mSystemResources; } @Override @@ -174,7 +292,7 @@ public final class BridgeContext extends Context { @Override public Object getSystemService(String service) { if (LAYOUT_INFLATER_SERVICE.equals(service)) { - return mInflater; + return mBridgeInflater; } // AutoCompleteTextView and MultiAutoCompleteTextView want a window @@ -183,20 +301,25 @@ public final class BridgeContext extends Context { return null; } + // needed by SearchView + if (INPUT_METHOD_SERVICE.equals(service)) { + return null; + } + throw new UnsupportedOperationException("Unsupported Service: " + service); } @Override public final TypedArray obtainStyledAttributes(int[] attrs) { - return createStyleBasedTypedArray(mThemeValues, attrs); + return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs); } @Override public final TypedArray obtainStyledAttributes(int resid, int[] attrs) throws Resources.NotFoundException { - // get the IStyleResourceValue based on the resId; - IStyleResourceValue style = getStyleByDynamicId(resid); + // get the StyleResourceValue based on the resId; + StyleResourceValue style = getStyleByDynamicId(resid); if (style == null) { throw new Resources.NotFoundException(); @@ -241,63 +364,128 @@ public final class BridgeContext extends Context { public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { + Map<String, String> defaultPropMap = null; + boolean isPlatformFile = true; + // Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java - BridgeXmlBlockParser parser = null; if (set instanceof BridgeXmlBlockParser) { + BridgeXmlBlockParser parser = null; parser = (BridgeXmlBlockParser)set; + + isPlatformFile = parser.isPlatformFile(); + + Object key = parser.getViewCookie(); + if (key != null) { + defaultPropMap = mDefaultPropMaps.get(key); + if (defaultPropMap == null) { + defaultPropMap = new HashMap<String, String>(); + mDefaultPropMaps.put(key, defaultPropMap); + } + } + + } else if (set instanceof BridgeLayoutParamsMapAttributes) { + // this is only for temp layout params generated dynamically, so this is never + // platform content. + isPlatformFile = false; } else if (set != null) { // null parser is ok // really this should not be happening since its instantiated in Bridge - mLogger.error("Parser is not a BridgeXmlBlockParser!"); + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Parser is not a BridgeXmlBlockParser!", null /*data*/); return null; } boolean[] frameworkAttributes = new boolean[1]; TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, frameworkAttributes); - BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length, - parser != null ? parser.isPlatformFile() : true); + BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, + isPlatformFile); // resolve the defStyleAttr value into a IStyleResourceValue - IStyleResourceValue defStyleValues = null; + StyleResourceValue defStyleValues = null; // look for a custom style. String customStyle = null; - if (parser != null) { - customStyle = parser.getAttributeValue(null /* namespace*/, "style"); + if (set != null) { + customStyle = set.getAttributeValue(null /* namespace*/, "style"); } if (customStyle != null) { - IResourceValue item = findResValue(customStyle, false /*forceFrameworkOnly*/); + ResourceValue item = mRenderResources.findResValue(customStyle, + false /*forceFrameworkOnly*/); + + // resolve it in case it links to something else + item = mRenderResources.resolveResValue(item); - if (item instanceof IStyleResourceValue) { - defStyleValues = (IStyleResourceValue)item; + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } } - if (defStyleValues == null && defStyleAttr != 0) { - // get the name from the int. - String defStyleName = searchAttr(defStyleAttr); + if (defStyleValues == null) { + if (defStyleAttr != 0) { + // get the name from the int. + String defStyleName = searchAttr(defStyleAttr); - // look for the style in the current theme, and its parent: - if (mThemeValues != null) { - IResourceValue item = findItemInStyle(mThemeValues, defStyleName); + if (defaultPropMap != null) { + defaultPropMap.put("style", defStyleName); + } + + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(defStyleName); if (item != null) { // item is a reference to a style entry. Search for it. - item = findResValue(item.getValue(), false /*forceFrameworkOnly*/); + item = mRenderResources.findResValue(item.getValue(), + false /*forceFrameworkOnly*/); - if (item instanceof IStyleResourceValue) { - defStyleValues = (IStyleResourceValue)item; + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } } else { - // TODO: log the error properly - System.out.println("Failed to find defStyle: " + defStyleName); + Bridge.getLog().error(null, + String.format( + "Failed to find style '%s' in current theme", defStyleName), + null /*data*/); + } + } else if (defStyleRes != 0) { + Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); + if (value == null) { + value = mProjectCallback.resolveResourceId(defStyleRes); } - } - } - if (defStyleRes != 0) { - // FIXME: See what we need to do with this. - throw new UnsupportedOperationException(); + if (value != null) { + if (value.getFirst() == ResourceType.STYLE) { + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); + if (item != null) { + if (item instanceof StyleResourceValue) { + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); + } + + defStyleValues = (StyleResourceValue)item; + } + } else { + Bridge.getLog().error(null, + String.format( + "Style with id 0x%x (resolved to '%s') does not exist.", + defStyleRes, value.getSecond()), + null /*data*/); + } + } else { + Bridge.getLog().error(null, + String.format( + "Resouce id 0x%x is not of type STYLE (instead %s)", + defStyleRes, value.getFirst().toString()), + null /*data*/); + } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style with id 0x%x in current theme", + defStyleRes), + null /*data*/); + } + } } String namespace = BridgeConstants.NS_RESOURCES; @@ -312,37 +500,43 @@ public final class BridgeContext extends Context { String name = styleAttribute.getValue(); String value = null; - if (parser != null) { - value = parser.getAttributeValue(namespace, name); + if (set != null) { + value = set.getAttributeValue(namespace, name); } // if there's no direct value for this attribute in the XML, we look for default // values in the widget defStyle, and then in the theme. if (value == null) { - IResourceValue resValue = null; + ResourceValue resValue = null; // look for the value in the defStyle first (and its parent if needed) if (defStyleValues != null) { - resValue = findItemInStyle(defStyleValues, name); + resValue = mRenderResources.findItemInStyle(defStyleValues, name); } // if the item is not present in the defStyle, we look in the main theme (and // its parent themes) - if (resValue == null && mThemeValues != null) { - resValue = findItemInStyle(mThemeValues, name); + if (resValue == null) { + resValue = mRenderResources.findItemInTheme(name); } // if we found a value, we make sure this doesn't reference another value. // So we resolve it. if (resValue != null) { - resValue = resolveResValue(resValue); + // put the first default value, before the resolution. + if (defaultPropMap != null) { + defaultPropMap.put(name, resValue.getValue()); + } + + resValue = mRenderResources.resolveResValue(resValue); } ta.bridgeSetValue(index, name, resValue); } else { // there is a value in the XML, but we need to resolve it in case it's // referencing another resource or a theme value. - ta.bridgeSetValue(index, name, resolveValue(null, name, value)); + ta.bridgeSetValue(index, name, + mRenderResources.resolveValue(null, name, value, isPlatformFile)); } } } @@ -365,11 +559,11 @@ public final class BridgeContext extends Context { * values found in the given style. * @see #obtainStyledAttributes(int, int[]) */ - private BridgeTypedArray createStyleBasedTypedArray(IStyleResourceValue style, int[] attrs) + private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs) throws Resources.NotFoundException { TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, null); - BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length, + BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, false /* platformResourceFlag */); // loop through all the values in the style map, and init the TypedArray with @@ -380,10 +574,10 @@ public final class BridgeContext extends Context { String name = styleAttribute.getValue(); // get the value from the style, or its parent styles. - IResourceValue resValue = findItemInStyle(style, name); + ResourceValue resValue = mRenderResources.findItemInStyle(style, name); // resolve it to make sure there are no references left. - ta.bridgeSetValue(index, name, resolveResValue(resValue)); + ta.bridgeSetValue(index, name, mRenderResources.resolveResValue(resValue)); } ta.sealArray(); @@ -393,274 +587,6 @@ public final class BridgeContext extends Context { /** - * Resolves the value of a resource, if the value references a theme or resource value. - * <p/> - * This method ensures that it returns a {@link IResourceValue} object that does not - * reference another resource. - * If the resource cannot be resolved, it returns <code>null</code>. - * <p/> - * If a value that does not need to be resolved is given, the method will return a new - * instance of IResourceValue that contains the input value. - * - * @param type the type of the resource - * @param name the name of the attribute containing this value. - * @param value the resource value, or reference to resolve - * @return the resolved resource value or <code>null</code> if it failed to resolve it. - */ - private IResourceValue resolveValue(String type, String name, String value) { - if (value == null) { - return null; - } - - // get the IResourceValue referenced by this value - IResourceValue resValue = findResValue(value, false /*forceFrameworkOnly*/); - - // if resValue is null, but value is not null, this means it was not a reference. - // we return the name/value wrapper in a IResourceValue - if (resValue == null) { - return new ResourceValue(type, name, value); - } - - // we resolved a first reference, but we need to make sure this isn't a reference also. - return resolveResValue(resValue); - } - - /** - * Returns the {@link IResourceValue} referenced by the value of <var>value</var>. - * <p/> - * This method ensures that it returns a {@link IResourceValue} object that does not - * reference another resource. - * If the resource cannot be resolved, it returns <code>null</code>. - * <p/> - * If a value that does not need to be resolved is given, the method will return the input - * value. - * - * @param value the value containing the reference to resolve. - * @return a {@link IResourceValue} object or <code>null</code> - */ - IResourceValue resolveResValue(IResourceValue value) { - if (value == null) { - return null; - } - - // if the resource value is a style, we simply return it. - if (value instanceof IStyleResourceValue) { - return value; - } - - // else attempt to find another IResourceValue referenced by this one. - IResourceValue resolvedValue = findResValue(value.getValue(), value.isFramework()); - - // if the value did not reference anything, then we simply return the input value - if (resolvedValue == null) { - return value; - } - - // otherwise, we attempt to resolve this new value as well - return resolveResValue(resolvedValue); - } - - /** - * Searches for, and returns a {@link IResourceValue} by its reference. - * <p/> - * The reference format can be: - * <pre>@resType/resName</pre> - * <pre>@android:resType/resName</pre> - * <pre>@resType/android:resName</pre> - * <pre>?resType/resName</pre> - * <pre>?android:resType/resName</pre> - * <pre>?resType/android:resName</pre> - * Any other string format will return <code>null</code>. - * <p/> - * The actual format of a reference is <pre>@[namespace:]resType/resName</pre> but this method - * only support the android namespace. - * - * @param reference the resource reference to search for. - * @param forceFrameworkOnly if true all references are considered to be toward framework - * resource even if the reference does not include the android: prefix. - * @return a {@link IResourceValue} or <code>null</code>. - */ - IResourceValue findResValue(String reference, boolean forceFrameworkOnly) { - if (reference == null) { - return null; - } - if (reference.startsWith(BridgeConstants.PREFIX_THEME_REF)) { - // no theme? no need to go further! - if (mThemeValues == null) { - return null; - } - - boolean frameworkOnly = false; - - // eleminate the prefix from the string - if (reference.startsWith(BridgeConstants.PREFIX_ANDROID_THEME_REF)) { - frameworkOnly = true; - reference = reference.substring(BridgeConstants.PREFIX_ANDROID_THEME_REF.length()); - } else { - reference = reference.substring(BridgeConstants.PREFIX_THEME_REF.length()); - } - - // at this point, value can contain type/name (drawable/foo for instance). - // split it to make sure. - String[] segments = reference.split("\\/"); - - // we look for the referenced item name. - String referenceName = null; - - if (segments.length == 2) { - // there was a resType in the reference. If it's attr, we ignore it - // else, we assert for now. - if (BridgeConstants.RES_ATTR.equals(segments[0])) { - referenceName = segments[1]; - } else { - // At this time, no support for ?type/name where type is not "attr" - return null; - } - } else { - // it's just an item name. - referenceName = segments[0]; - } - - // now we look for android: in the referenceName in order to support format - // such as: ?attr/android:name - if (referenceName.startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - referenceName = referenceName.substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - // Now look for the item in the theme, starting with the current one. - if (frameworkOnly) { - // FIXME for now we do the same as if it didn't specify android: - return findItemInStyle(mThemeValues, referenceName); - } - - return findItemInStyle(mThemeValues, referenceName); - } else if (reference.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { - boolean frameworkOnly = false; - - // check for the specific null reference value. - if (BridgeConstants.REFERENCE_NULL.equals(reference)) { - return null; - } - - // Eliminate the prefix from the string. - if (reference.startsWith(BridgeConstants.PREFIX_ANDROID_RESOURCE_REF)) { - frameworkOnly = true; - reference = reference.substring( - BridgeConstants.PREFIX_ANDROID_RESOURCE_REF.length()); - } else { - reference = reference.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); - } - - // at this point, value contains type/[android:]name (drawable/foo for instance) - String[] segments = reference.split("\\/"); - - // now we look for android: in the resource name in order to support format - // such as: @drawable/android:name - if (segments[1].startsWith(BridgeConstants.PREFIX_ANDROID)) { - frameworkOnly = true; - segments[1] = segments[1].substring(BridgeConstants.PREFIX_ANDROID.length()); - } - - return findResValue(segments[0], segments[1], - forceFrameworkOnly ? true :frameworkOnly); - } - - // Looks like the value didn't reference anything. Return null. - return null; - } - - /** - * Searches for, and returns a {@link IResourceValue} by its name, and type. - * @param resType the type of the resource - * @param resName the name of the resource - * @param frameworkOnly if <code>true</code>, the method does not search in the - * project resources - */ - private IResourceValue findResValue(String resType, String resName, boolean frameworkOnly) { - // map of IResouceValue for the given type - Map<String, IResourceValue> typeMap; - - // if allowed, search in the project resources first. - if (frameworkOnly == false) { - typeMap = mProjectResources.get(resType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } - } - } - - // now search in the framework resources. - typeMap = mFrameworkResources.get(resType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } - } - - // didn't find the resource anywhere. - return null; - } - - /** - * Returns a framework resource by type and name. The returned resource is resolved. - * @param resourceType the type of the resource - * @param resourceName the name of the resource - */ - public IResourceValue getFrameworkResource(String resourceType, String resourceName) { - return getResource(resourceType, resourceName, mFrameworkResources); - } - - /** - * Returns a project resource by type and name. The returned resource is resolved. - * @param resourceType the type of the resource - * @param resourceName the name of the resource - */ - public IResourceValue getProjectResource(String resourceType, String resourceName) { - return getResource(resourceType, resourceName, mProjectResources); - } - - IResourceValue getResource(String resourceType, String resourceName, - Map<String, Map<String, IResourceValue>> resourceRepository) { - Map<String, IResourceValue> typeMap = resourceRepository.get(resourceType); - if (typeMap != null) { - IResourceValue item = typeMap.get(resourceName); - if (item != null) { - item = resolveResValue(item); - return item; - } - } - - // didn't find the resource anywhere. - return null; - - } - - /** - * Returns the {@link IResourceValue} matching a given name in a given style. If the - * item is not directly available in the style, the method looks in its parent style. - * @param style the style to search in - * @param itemName the name of the item to search for. - * @return the {@link IResourceValue} object or <code>null</code> - */ - IResourceValue findItemInStyle(IStyleResourceValue style, String itemName) { - IResourceValue item = style.findItem(itemName); - - // if we didn't find it, we look in the parent style (if applicable) - if (item == null && mStyleInheritanceMap != null) { - IStyleResourceValue parentStyle = mStyleInheritanceMap.get(style); - if (parentStyle != null) { - return findItemInStyle(parentStyle, itemName); - } - } - - return item; - } - - /** * The input int[] attrs is one of com.android.internal.R.styleable fields where the name * of the field is the style being referenced and the array contains one index per attribute. * <p/> @@ -675,14 +601,14 @@ public final class BridgeContext extends Context { */ private TreeMap<Integer,String> searchAttrs(int[] attrs, boolean[] outFrameworkFlag) { // get the name of the array from the framework resources - String arrayName = Bridge.resolveResourceValue(attrs); + String arrayName = Bridge.resolveResourceId(attrs); if (arrayName != null) { // if we found it, get the name of each of the int in the array. TreeMap<Integer,String> attributes = new TreeMap<Integer, String>(); for (int i = 0 ; i < attrs.length ; i++) { - String[] info = Bridge.resolveResourceValue(attrs[i]); + Pair<ResourceType, String> info = Bridge.resolveResourceId(attrs[i]); if (info != null) { - attributes.put(i, info[0]); + attributes.put(i, info.getSecond()); } else { // FIXME Not sure what we should be doing here... attributes.put(i, null); @@ -698,13 +624,13 @@ public final class BridgeContext extends Context { // if the name was not found in the framework resources, look in the project // resources - arrayName = mProjectCallback.resolveResourceValue(attrs); + arrayName = mProjectCallback.resolveResourceId(attrs); if (arrayName != null) { TreeMap<Integer,String> attributes = new TreeMap<Integer, String>(); for (int i = 0 ; i < attrs.length ; i++) { - String[] info = mProjectCallback.resolveResourceValue(attrs[i]); + Pair<ResourceType, String> info = mProjectCallback.resolveResourceId(attrs[i]); if (info != null) { - attributes.put(i, info[0]); + attributes.put(i, info.getSecond()); } else { // FIXME Not sure what we should be doing here... attributes.put(i, null); @@ -729,24 +655,24 @@ public final class BridgeContext extends Context { * if nothing is found. */ public String searchAttr(int attr) { - String[] info = Bridge.resolveResourceValue(attr); + Pair<ResourceType, String> info = Bridge.resolveResourceId(attr); if (info != null) { - return info[0]; + return info.getSecond(); } - info = mProjectCallback.resolveResourceValue(attr); + info = mProjectCallback.resolveResourceId(attr); if (info != null) { - return info[0]; + return info.getSecond(); } return null; } - int getDynamicIdByStyle(IStyleResourceValue resValue) { + int getDynamicIdByStyle(StyleResourceValue resValue) { if (mDynamicIdToStyleMap == null) { // create the maps. - mDynamicIdToStyleMap = new HashMap<Integer, IStyleResourceValue>(); - mStyleToDynamicIdMap = new HashMap<IStyleResourceValue, Integer>(); + mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>(); + mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>(); } // look for an existing id @@ -764,7 +690,7 @@ public final class BridgeContext extends Context { return id; } - private IStyleResourceValue getStyleByDynamicId(int i) { + private StyleResourceValue getStyleByDynamicId(int i) { if (mDynamicIdToStyleMap != null) { return mDynamicIdToStyleMap.get(i); } @@ -772,8 +698,8 @@ public final class BridgeContext extends Context { return null; } - int getFrameworkIdValue(String idName, int defValue) { - Integer value = Bridge.getResourceValue(BridgeConstants.RES_ID, idName); + int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) { + Integer value = Bridge.getResourceId(resType, resName); if (value != null) { return value.intValue(); } @@ -781,9 +707,9 @@ public final class BridgeContext extends Context { return defValue; } - int getProjectIdValue(String idName, int defValue) { + int getProjectResourceValue(ResourceType resType, String resName, int defValue) { if (mProjectCallback != null) { - Integer value = mProjectCallback.getResourceValue(BridgeConstants.RES_ID, idName); + Integer value = mProjectCallback.getResourceId(resType, resName); if (value != null) { return value.intValue(); } @@ -1000,7 +926,7 @@ public final class BridgeContext extends Context { @Override public ApplicationInfo getApplicationInfo() { - return new ApplicationInfo(); + return mApplicationInfo; } @Override @@ -1043,25 +969,20 @@ public final class BridgeContext extends Context { } - @SuppressWarnings("unused") @Override - public FileInputStream openFileInput(String arg0) - throws FileNotFoundException { + public FileInputStream openFileInput(String arg0) throws FileNotFoundException { // TODO Auto-generated method stub return null; } - @SuppressWarnings("unused") @Override - public FileOutputStream openFileOutput(String arg0, int arg1) - throws FileNotFoundException { + public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException { // TODO Auto-generated method stub return null; } @Override - public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, - CursorFactory arg2) { + public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) { // TODO Auto-generated method stub return null; } @@ -1142,14 +1063,12 @@ public final class BridgeContext extends Context { } - @SuppressWarnings("unused") @Override public void setWallpaper(Bitmap arg0) throws IOException { // TODO Auto-generated method stub } - @SuppressWarnings("unused") @Override public void setWallpaper(InputStream arg0) throws IOException { // TODO Auto-generated method stub @@ -1204,4 +1123,9 @@ public final class BridgeContext extends Context { public Context getApplicationContext() { throw new UnsupportedOperationException(); } + + @Override + public boolean isRestricted() { + return false; + } } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index 0910d79..cb8d8dd 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -14,36 +14,42 @@ * limitations under the License. */ -package android.view; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.MergeCookie; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; -import com.android.layoutlib.bridge.BridgeContext; -import com.android.layoutlib.bridge.BridgeXmlBlockParser; +import com.android.resources.ResourceType; +import com.android.util.Pair; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.util.AttributeSet; +import android.view.InflateException; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import java.io.File; import java.io.FileReader; /** - * Custom implementation of {@link LayoutInflater} to handle custom views. + * Custom implementation of {@link LayoutInflater} to handle custom views. */ public final class BridgeInflater extends LayoutInflater { - + private final IProjectCallback mProjectCallback; + private boolean mIsInMerge = false; /** * List of class prefixes which are tried first by default. * <p/> * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. - */ + */ private static final String[] sClassPrefixList = { "android.widget.", "android.webkit." @@ -53,10 +59,10 @@ public final class BridgeInflater extends LayoutInflater { super(original, newContext); mProjectCallback = null; } - + /** * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. - * + * * @param context The Android application context. * @param projectCallback the {@link IProjectCallback} object. */ @@ -82,7 +88,7 @@ public final class BridgeInflater extends LayoutInflater { // Ignore. We'll try again using the base class below. } } - + // Next try using the parent loader. This will most likely only work for // fully-qualified class names. try { @@ -92,7 +98,7 @@ public final class BridgeInflater extends LayoutInflater { } catch (ClassNotFoundException e) { // Ignore. We'll try again using the custom view loader below. } - + // Finally try again using the custom view loader try { if (view == null) { @@ -109,12 +115,12 @@ public final class BridgeInflater extends LayoutInflater { ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); throw exception; } - + setupViewInContext(view, attrs); - + return view; } - + @Override public View createViewFromTag(String name, AttributeSet attrs) { View view = null; @@ -128,7 +134,7 @@ public final class BridgeInflater extends LayoutInflater { // Wrap the real exception in an InflateException so that the calling // method can deal with it. InflateException exception = new InflateException(); - if (e2.getClass().equals(ClassNotFoundException.class) == false) { + if (e2.getClass().equals(ClassNotFoundException.class) == false) { exception.initCause(e2); } else { exception.initCause(e); @@ -136,30 +142,30 @@ public final class BridgeInflater extends LayoutInflater { throw exception; } } - + setupViewInContext(view, attrs); - + return view; } - + @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; - - IResourceValue value = null; - String[] layoutInfo = Bridge.resolveResourceValue(resource); + ResourceValue value = null; + + Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource); if (layoutInfo != null) { - value = bridgeContext.getFrameworkResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); + value = bridgeContext.getRenderResources().getFrameworkResource( + ResourceType.LAYOUT, layoutInfo.getSecond()); } else { - layoutInfo = mProjectCallback.resolveResourceValue(resource); - + layoutInfo = mProjectCallback.resolveResourceId(resource); + if (layoutInfo != null) { - value = bridgeContext.getProjectResource(BridgeConstants.RES_LAYOUT, - layoutInfo[0]); + value = bridgeContext.getRenderResources().getProjectResource( + ResourceType.LAYOUT, layoutInfo.getSecond()); } } @@ -170,21 +176,23 @@ public final class BridgeInflater extends LayoutInflater { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - + BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, false); - + return inflate(bridgeParser, root); } catch (Exception e) { - bridgeContext.getLogger().error(e); - // return null below. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/); + + return null; } } } } return null; } - + private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException, Exception{ if (mProjectCallback != null) { @@ -192,12 +200,12 @@ public final class BridgeInflater extends LayoutInflater { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } - + mConstructorArgs[1] = attrs; Object customView = mProjectCallback.loadView(name, mConstructorSignature, mConstructorArgs); - + if (customView instanceof View) { return (View)customView; } @@ -205,21 +213,43 @@ public final class BridgeInflater extends LayoutInflater { return null; } - - - + private void setupViewInContext(View view, AttributeSet attrs) { if (getContext() instanceof BridgeContext) { BridgeContext bc = (BridgeContext) getContext(); if (attrs instanceof BridgeXmlBlockParser) { - Object viewKey = ((BridgeXmlBlockParser) attrs).getViewKey(); + BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; + + // get the view key + Object viewKey = parser.getViewCookie(); + + // if there's no view key and the depth is 1 (ie this is the first tag), or 2 + // (this is first item in included merge layout) + // look for a previous parser in the context, and check if this one has a viewkey. + int testDepth = mIsInMerge ? 2 : 1; + if (viewKey == null && parser.getDepth() == testDepth) { + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + viewKey = previousParser.getViewCookie(); + } + } + if (viewKey != null) { + if (testDepth == 2) { + // include-merge case + viewKey = new MergeCookie(viewKey); + } + bc.addViewKey(view, viewKey); } } } } + public void setIsInMerge(boolean isInMerge) { + mIsInMerge = isInMerge; + } + @Override public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java new file mode 100644 index 0000000..d208408 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.BridgeConstants; + +import android.util.AttributeSet; + +import java.util.Map; + +/** + * An implementation of the {@link AttributeSet} interface on top of a map of attribute in the form + * of (name, value). + * + * This is meant to be called only from {@link BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)} + * in the case of LayoutParams and therefore isn't a full implementation. + */ +public class BridgeLayoutParamsMapAttributes implements AttributeSet { + + private final Map<String, String> mAttributes; + + public BridgeLayoutParamsMapAttributes(Map<String, String> attributes) { + mAttributes = attributes; + } + + public String getAttributeValue(String namespace, String name) { + if (BridgeConstants.NS_RESOURCES.equals(namespace)) { + return mAttributes.get(name); + } + + return null; + } + + // ---- the following methods are not called from + // BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int) + // Should they ever be called, we'll just implement them on a need basis. + + public int getAttributeCount() { + throw new UnsupportedOperationException(); + } + + public String getAttributeName(int index) { + throw new UnsupportedOperationException(); + } + + public String getAttributeValue(int index) { + throw new UnsupportedOperationException(); + } + + public String getPositionDescription() { + throw new UnsupportedOperationException(); + } + + public int getAttributeNameResource(int index) { + throw new UnsupportedOperationException(); + } + + public int getAttributeListValue(String namespace, String attribute, + String[] options, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public boolean getAttributeBooleanValue(String namespace, String attribute, + boolean defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeResourceValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeIntValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeUnsignedIntValue(String namespace, String attribute, + int defaultValue) { + throw new UnsupportedOperationException(); + } + + public float getAttributeFloatValue(String namespace, String attribute, + float defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeListValue(int index, + String[] options, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public boolean getAttributeBooleanValue(int index, boolean defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeResourceValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeIntValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getAttributeUnsignedIntValue(int index, int defaultValue) { + throw new UnsupportedOperationException(); + } + + public float getAttributeFloatValue(int index, float defaultValue) { + throw new UnsupportedOperationException(); + } + + public String getIdAttribute() { + throw new UnsupportedOperationException(); + } + + public String getClassAttribute() { + throw new UnsupportedOperationException(); + } + + public int getIdAttributeResourceValue(int defaultValue) { + throw new UnsupportedOperationException(); + } + + public int getStyleAttribute() { + throw new UnsupportedOperationException(); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index 6358abb..5e5aeb1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -14,10 +14,17 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IProjectCallback; -import com.android.layoutlib.api.IResourceValue; +package com.android.layoutlib.bridge.android; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.ninepatch.NinePatch; +import com.android.resources.ResourceType; +import com.android.util.Pair; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; @@ -52,6 +59,35 @@ public final class BridgeResources extends Resources { private boolean[] mPlatformResourceFlag = new boolean[1]; /** + * Simpler wrapper around FileInputStream. This is used when the input stream represent + * not a normal bitmap but a nine patch. + * This is useful when the InputStream is created in a method but used in another that needs + * to know whether this is 9-patch or not, such as BitmapFactory. + */ + public class NinePatchInputStream extends FileInputStream { + private boolean mFakeMarkSupport = true; + public NinePatchInputStream(File file) throws FileNotFoundException { + super(file); + } + + @Override + public boolean markSupported() { + if (mFakeMarkSupport) { + // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream. + return true; + } + + return super.markSupported(); + } + + public void disableFakeMarkSupport() { + // disable fake mark support so that in case codec actually try to use them + // we don't lie to them. + mFakeMarkSupport = false; + } + } + + /** * This initializes the static field {@link Resources#mSystem} which is used * by methods who get global resources using {@link Resources#getSystem()}. * <p/> @@ -64,21 +100,18 @@ public final class BridgeResources extends Resources { DisplayMetrics metrics, Configuration config, IProjectCallback projectCallback) { - if (!(Resources.mSystem instanceof BridgeResources)) { - Resources.mSystem = new BridgeResources(context, - assets, - metrics, - config, - projectCallback); - } - return Resources.mSystem; + return Resources.mSystem = new BridgeResources(context, + assets, + metrics, + config, + projectCallback); } /** - * Clears the static {@link Resources#mSystem} to make sure we don't leave objects + * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects * around that would prevent us from unloading the library. */ - /*package*/ static void clearSystem() { + /*package*/ static void disposeSystem() { if (Resources.mSystem instanceof BridgeResources) { ((BridgeResources)(Resources.mSystem)).mContext = null; ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; @@ -97,22 +130,24 @@ public final class BridgeResources extends Resources { return new BridgeTypedArray(this, mContext, numEntries, platformFile); } - private IResourceValue getResourceValue(int id, boolean[] platformResFlag_out) { + private ResourceValue getResourceValue(int id, boolean[] platformResFlag_out) { // first get the String related to this id in the framework - String[] resourceInfo = Bridge.resolveResourceValue(id); + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); if (resourceInfo != null) { platformResFlag_out[0] = true; - return mContext.getFrameworkResource(resourceInfo[1], resourceInfo[0]); + return mContext.getRenderResources().getFrameworkResource( + resourceInfo.getFirst(), resourceInfo.getSecond()); } // didn't find a match in the framework? look in the project. if (mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceValue(id); + resourceInfo = mProjectCallback.resolveResourceId(id); if (resourceInfo != null) { platformResFlag_out[0] = false; - return mContext.getProjectResource(resourceInfo[1], resourceInfo[0]); + return mContext.getRenderResources().getProjectResource( + resourceInfo.getFirst(), resourceInfo.getSecond()); } } @@ -121,10 +156,10 @@ public final class BridgeResources extends Resources { @Override public Drawable getDrawable(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - return ResourceHelper.getDrawable(value, mContext, value.isFramework()); + return ResourceHelper.getDrawable(value, mContext); } // id was not found or not resolved. Throw a NotFoundException. @@ -136,12 +171,14 @@ public final class BridgeResources extends Resources { @Override public int getColor(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { try { return ResourceHelper.getColor(value.getValue()); } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, + null /*data*/); return 0; } } @@ -155,14 +192,12 @@ public final class BridgeResources extends Resources { @Override public ColorStateList getColorStateList(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue resValue = getResourceValue(id, mPlatformResourceFlag); - if (value != null) { - try { - int color = ResourceHelper.getColor(value.getValue()); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - return null; + if (resValue != null) { + ColorStateList stateList = ResourceHelper.getColorStateList(resValue, mContext); + if (stateList != null) { + return stateList; } } @@ -175,7 +210,7 @@ public final class BridgeResources extends Resources { @Override public CharSequence getText(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { return value.getValue(); @@ -190,27 +225,76 @@ public final class BridgeResources extends Resources { @Override public XmlResourceParser getLayout(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - File xml = new File(value.getValue()); - if (xml.isFile()) { - // we need to create a pull parser around the layout XML file, and then - // give that to our XmlBlockParser - try { - KXmlParser parser = new KXmlParser(); + XmlPullParser parser = null; + + try { + // check if the current parser can provide us with a custom parser. + BridgeXmlBlockParser currentParser = mContext.getCurrentParser(); + if (currentParser != null) { + parser = currentParser.getParser(value.getName()); + } + + // create a new one manually if needed. + if (parser == null) { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(xml)); + } + } + + if (parser != null) { + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } + + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + XmlPullParser parser = null; + + try { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(xml)); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); - } catch (XmlPullParserException e) { - mContext.getLogger().error(e); - - // we'll return null below. - } catch (FileNotFoundException e) { - // this shouldn't happen since we check above. } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. } + } // id was not found or not resolved. Throw a NotFoundException. @@ -233,7 +317,7 @@ public final class BridgeResources extends Resources { @Override public float getDimension(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -262,7 +346,7 @@ public final class BridgeResources extends Resources { @Override public int getDimensionPixelOffset(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -284,7 +368,7 @@ public final class BridgeResources extends Resources { @Override public int getDimensionPixelSize(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -306,7 +390,7 @@ public final class BridgeResources extends Resources { @Override public int getInteger(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null && value.getValue() != null) { String v = value.getValue(); @@ -361,7 +445,7 @@ public final class BridgeResources extends Resources { @Override public String getString(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null && value.getValue() != null) { return value.getValue(); @@ -377,7 +461,7 @@ public final class BridgeResources extends Resources { @Override public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -406,7 +490,7 @@ public final class BridgeResources extends Resources { @Override public XmlResourceParser getXml(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { String v = value.getValue(); @@ -470,16 +554,22 @@ public final class BridgeResources extends Resources { @Override public InputStream openRawResource(int id) throws NotFoundException { - IResourceValue value = getResourceValue(id, mPlatformResourceFlag); + ResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - String v = value.getValue(); + String path = value.getValue(); - if (v != null) { + if (path != null) { // check this is a file - File f = new File(value.getValue()); + File f = new File(path); if (f.isFile()) { try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } return new FileInputStream(f); } catch (FileNotFoundException e) { NotFoundException newE = new NotFoundException(); @@ -501,9 +591,17 @@ public final class BridgeResources extends Resources { public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { getValue(id, value, true); - File f = new File(value.string.toString()); + String path = value.string.toString(); + + File f = new File(path); if (f.isFile()) { try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } return new FileInputStream(f); } catch (FileNotFoundException e) { NotFoundException exception = new NotFoundException(); @@ -527,18 +625,18 @@ public final class BridgeResources extends Resources { */ private void throwException(int id) throws NotFoundException { // first get the String related to this id in the framework - String[] resourceInfo = Bridge.resolveResourceValue(id); + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); // if the name is unknown in the framework, get it from the custom view loader. if (resourceInfo == null && mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceValue(id); + resourceInfo = mProjectCallback.resolveResourceId(id); } String message = null; if (resourceInfo != null) { message = String.format( "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", - resourceInfo[1], id, resourceInfo[0]); + resourceInfo.getFirst(), id, resourceInfo.getSecond()); } else { message = String.format( "Could not resolve resource value: 0x%1$X.", id); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index 70c5bd7..c1d7600 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -14,14 +14,21 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -33,39 +40,38 @@ import android.view.ViewGroup.LayoutParams; import java.io.File; import java.io.FileReader; +import java.util.Arrays; import java.util.Map; /** - * TODO: describe. + * Custom implementation of TypedArray to handle non compiled resources. */ public final class BridgeTypedArray extends TypedArray { - @SuppressWarnings("hiding") - private BridgeResources mResources; + private BridgeResources mBridgeResources; private BridgeContext mContext; - @SuppressWarnings("hiding") - private IResourceValue[] mData; + private ResourceValue[] mResourceData; private String[] mNames; private final boolean mPlatformFile; public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { super(null, null, null, 0); - mResources = resources; + mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; - mData = new IResourceValue[len]; + mResourceData = new ResourceValue[len]; mNames = new String[len]; } /** A bridge-specific method that sets a value in the type array */ - public void bridgeSetValue(int index, String name, IResourceValue value) { - mData[index] = value; + public void bridgeSetValue(int index, String name, ResourceValue value) { + mResourceData[index] = value; mNames[index] = name; } /** - * Seals the array after all calls to {@link #bridgeSetValue(int, String, IResourceValue)} have + * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have * been done. * <p/>This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. @@ -74,7 +80,7 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; - for (IResourceValue data : mData) { + for (ResourceValue data : mResourceData) { if (data != null) { count++; } @@ -86,8 +92,8 @@ public final class BridgeTypedArray extends TypedArray { // fill the array with the indices. int index = 1; - for (int i = 0 ; i < mData.length ; i++) { - if (mData[i] != null) { + for (int i = 0 ; i < mResourceData.length ; i++) { + if (mResourceData[i] != null) { mIndices[index++] = i; } } @@ -98,7 +104,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int length() { - return mData.length; + return mResourceData.length; } /** @@ -106,7 +112,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Resources getResources() { - return mResources; + return mBridgeResources; } /** @@ -119,9 +125,9 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence getText(int index) { - if (mData[index] != null) { + if (mResourceData[index] != null) { // FIXME: handle styled strings! - return mData[index].getValue(); + return mResourceData[index].getValue(); } return null; @@ -137,8 +143,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public String getString(int index) { - if (mData[index] != null) { - return mData[index].getValue(); + if (mResourceData[index] != null) { + return mResourceData[index].getValue(); } return null; @@ -154,11 +160,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getBoolean(int index, boolean defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { return XmlUtils.convertValueToBoolean(s, defValue); } @@ -176,11 +182,15 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInt(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); + + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } try { return (s == null) ? defValue : XmlUtils.convertValueToInt(s, defValue); @@ -204,9 +214,10 @@ public final class BridgeTypedArray extends TypedArray { if (i != null) { result |= i.intValue(); } else { - mContext.getLogger().warning(String.format( - "Unknown constant \"%s\" in attribute \"%2$s\"", - keyword, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" is not a valid value", + keyword, mNames[index]), null /*data*/); } } return result; @@ -224,19 +235,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFloat(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { return Float.parseFloat(s); } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a float in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" cannot be converted to float.", + s, mNames[index]), null /*data*/); // we'll return the default value below. } @@ -258,19 +270,14 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getColor(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); - try { - return ResourceHelper.getColor(s); - } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a color in attribute \"%2$s\"", - s, mNames[index])); - - // we'll return the default value below. + ColorStateList colorStateList = ResourceHelper.getColorStateList( + mResourceData[index], mContext); + if (colorStateList != null) { + return colorStateList.getDefaultColor(); } return defValue; @@ -287,49 +294,56 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public ColorStateList getColorStateList(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + ResourceValue resValue = mResourceData[index]; + String value = resValue.getValue(); if (value == null) { return null; } - try { - int color = ResourceHelper.getColor(value); - return ColorStateList.valueOf(color); - } catch (NumberFormatException e) { - // if it's not a color value, we'll attempt to read the xml based color below. + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; } // let the framework inflate the ColorStateList from the XML file. - try { - File f = new File(value); - if (f.isFile()) { + File f = new File(value); + if (f.isFile()) { + try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - ColorStateList colorStateList = ColorStateList.createFromXml( - mContext.getResources(), - // FIXME: we need to know if this resource is platform or not - new BridgeXmlBlockParser(parser, mContext, false)); - return colorStateList; + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, mContext, resValue.isFramework()); + try { + return ColorStateList.createFromXml(mContext.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + return null; + } catch (Exception e) { + // this is an error and not warning since the file existence is checked before + // attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; } - } catch (Exception e) { - // this is an error and not warning since the file existence is checked before - // attempting to parse it. - mContext.getLogger().error(e); - - // return null below. } - // looks like were unable to resolve the color value. - mContext.getLogger().warning(String.format( - "Unable to resolve color value \"%1$s\" in attribute \"%2$s\"", - value, mNames[index])); + try { + int color = ResourceHelper.getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/); + } return null; } @@ -345,19 +359,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInteger(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s != null) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { - mContext.getLogger().warning(String.format( - "Unable to convert \"%s\" into a integer in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" cannont be converted to an integer.", + s, mNames[index]), null /*data*/); // The default value is returned below. } @@ -384,11 +399,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getDimension(int index, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -399,14 +414,19 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.WRAP_CONTENT; } + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } + if (ResourceHelper.stringToFloat(s, mValue)) { - return mValue.getDimension(mResources.mMetrics); + return mValue.getDimension(mBridgeResources.mMetrics); } // looks like we were unable to resolve the dimension value - mContext.getLogger().warning(String.format( - "Unable to resolve dimension value \"%1$s\" in attribute \"%2$s\"", - s, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null /*data*/); return defValue; } @@ -453,11 +473,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getDimensionPixelSize(int index, int defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); if (s == null) { return defValue; @@ -468,6 +488,10 @@ public final class BridgeTypedArray extends TypedArray { return LayoutParams.WRAP_CONTENT; } + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } + // FIXME huh? float f = getDimension(index, defValue); @@ -476,8 +500,11 @@ public final class BridgeTypedArray extends TypedArray { if (f == 0) return 0; if (f > 0) return 1; - throw new UnsupportedOperationException("Can't convert to dimension: " + - Integer.toString(index)); + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Can't convert to dimension: " + Integer.toString(index), + null, null /*data*/); + + return defValue; } /** @@ -519,11 +546,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFraction(int index, int base, int pbase, float defValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return defValue; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value == null) { return defValue; } @@ -533,9 +560,10 @@ public final class BridgeTypedArray extends TypedArray { } // looks like we were unable to resolve the fraction value - mContext.getLogger().warning(String.format( - "Unable to resolve fraction value \"%1$s\" in attribute \"%2$s\"", - value, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" cannont be converted to a fraction.", + value, mNames[index]), null /*data*/); return defValue; } @@ -556,8 +584,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getResourceId(int index, int defValue) { - // get the IResource for this index - IResourceValue resValue = mData[index]; + // get the Resource for this index + ResourceValue resValue = mResourceData[index]; // no data, return the default value. if (resValue == null) { @@ -565,24 +593,30 @@ public final class BridgeTypedArray extends TypedArray { } // check if this is a style resource - if (resValue instanceof IStyleResourceValue) { + if (resValue instanceof StyleResourceValue) { // get the id that will represent this style. - return mContext.getDynamicIdByStyle((IStyleResourceValue)resValue); + return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); + } + + if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) { + return defValue; } - // if the attribute was a reference to an id, and not a declaration of an id (@+id), then - // the xml attribute value was "resolved" which leads us to a IResourceValue with - // getType() returning "id" and getName() returning the id name + // if the attribute was a reference to a resource, and not a declaration of an id (@+id), + // then the xml attribute value was "resolved" which leads us to a ResourceValue with a + // valid getType() and getName() returning a resource name. // (and getValue() returning null!). We need to handle this! - if (resValue.getType() != null && resValue.getType().equals(BridgeConstants.RES_ID)) { + if (resValue.getResourceType() != null) { // if this is a framework id if (mPlatformFile || resValue.isFramework()) { // look for idName in the android R classes - return mContext.getFrameworkIdValue(resValue.getName(), defValue); + return mContext.getFrameworkResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); } // look for idName in the project R class. - return mContext.getProjectIdValue(resValue.getName(), defValue); + return mContext.getProjectResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); } // else, try to get the value, and resolve it somehow. @@ -619,29 +653,33 @@ public final class BridgeTypedArray extends TypedArray { // if this is a framework id if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) { // look for idName in the android R classes - return mContext.getFrameworkIdValue(idName, defValue); + return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); } // look for idName in the project R class. - return mContext.getProjectIdValue(idName, defValue); + return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); } // not a direct id valid reference? resolve it Integer idValue = null; if (resValue.isFramework()) { - idValue = Bridge.getResourceValue(resValue.getType(), resValue.getName()); + idValue = Bridge.getResourceId(resValue.getResourceType(), + resValue.getName()); } else { - idValue = mContext.getProjectCallback().getResourceValue( - resValue.getType(), resValue.getName()); + idValue = mContext.getProjectCallback().getResourceId( + resValue.getResourceType(), resValue.getName()); } if (idValue != null) { return idValue.intValue(); } - mContext.getLogger().warning(String.format( - "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index])); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, + String.format( + "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]), + resValue); + return defValue; } @@ -657,28 +695,17 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Drawable getDrawable(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - IResourceValue value = mData[index]; + ResourceValue value = mResourceData[index]; String stringValue = value.getValue(); - if (stringValue == null || BridgeConstants.REFERENCE_NULL.equals(stringValue)) { + if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) { return null; } - Drawable d = ResourceHelper.getDrawable(value, mContext, mData[index].isFramework()); - - if (d != null) { - return d; - } - - // looks like we were unable to resolve the drawable - mContext.getLogger().warning(String.format( - "Unable to resolve drawable \"%1$s\" in attribute \"%2$s\"", stringValue, - mNames[index])); - - return null; + return ResourceHelper.getDrawable(value, mContext); } @@ -694,18 +721,23 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return null; } - String value = mData[index].getValue(); + String value = mResourceData[index].getValue(); if (value != null) { + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; + } + return new CharSequence[] { value }; } - mContext.getLogger().warning(String.format( - String.format("Unknown value for getTextArray(%d) => %s", //DEBUG - index, mData[index].getName()))); + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + String.format("Unknown value for getTextArray(%d) => %s", //DEBUG + index, mResourceData[index].getName())), null /*data*/); return null; } @@ -721,11 +753,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getValue(int index, TypedValue outValue) { - if (mData[index] == null) { + if (mResourceData[index] == null) { return false; } - String s = mData[index].getValue(); + String s = mResourceData[index].getValue(); return ResourceHelper.stringToFloat(s, outValue); } @@ -739,7 +771,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean hasValue(int index) { - return mData[index] != null; + return mResourceData[index] != null; } /** @@ -786,6 +818,6 @@ public final class BridgeTypedArray extends TypedArray { @Override public String toString() { - return mData.toString(); + return Arrays.toString(mResourceData); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java new file mode 100644 index 0000000..0efa102 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.android; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.view.IWindow; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View.AttachInfo; + +/** + * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. + */ +public final class BridgeWindow implements IWindow { + + public void dispatchAppVisibility(boolean arg0) throws RemoteException { + // pass for now. + } + + public void dispatchGetNewSurface() throws RemoteException { + // pass for now. + } + + public void dispatchKey(KeyEvent arg0) throws RemoteException { + // pass for now. + } + + public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { + // pass for now. + } + + public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) + throws RemoteException { + // pass for now. + } + + public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) + throws RemoteException { + // pass for now. + } + + public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4, Configuration arg5) + throws RemoteException { + // pass for now. + } + + public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { + // pass for now. + } + + public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, + boolean sync) { + // pass for now. + } + + public void dispatchWallpaperCommand(String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + } + + public void closeSystemDialogs(String reason) { + // pass for now. + } + + public void dispatchSystemUiVisibilityChanged(int visibility) { + // pass for now. + } + + public IBinder asBinder() { + // pass for now. + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java new file mode 100644 index 0000000..7866bfa --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.android; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.Surface; +import android.view.SurfaceView; +import android.view.WindowManager.LayoutParams; + +/** + * Implementation of {@link IWindowSession} so that mSession is not null in + * the {@link SurfaceView}. + */ +public final class BridgeWindowSession implements IWindowSession { + + public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3, + InputChannel outInputchannel) + throws RemoteException { + // pass for now. + return 0; + } + + public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) + throws RemoteException { + // pass for now. + return 0; + } + + public void finishDrawing(IWindow arg0) throws RemoteException { + // pass for now. + } + + public boolean getInTouchMode() throws RemoteException { + // pass for now. + return false; + } + + public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { + // pass for now. + return false; + } + + public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4, + boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, Surface arg8) + throws RemoteException { + // pass for now. + return 0; + } + + public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { + // pass for now. + } + + public void remove(IWindow arg0) throws RemoteException { + // pass for now. + } + + public void setInTouchMode(boolean arg0) throws RemoteException { + // pass for now. + } + + public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { + // pass for now. + } + + public void setWallpaperPosition(IBinder window, float x, float y, + float xStep, float yStep) { + // pass for now. + } + + public void wallpaperOffsetsComplete(IBinder window) { + // pass for now. + } + + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + return null; + } + + public void wallpaperCommandComplete(IBinder window, Bundle result) { + // pass for now. + } + + public IBinder asBinder() { + // pass for now. + return null; + } + + public void setInsets(IWindow arg0, int arg1, Rect arg2, Rect arg3) throws RemoteException { + // TODO Auto-generated method stub + + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index d842a66..2f54ae6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IXmlPullParser; + +import com.android.ide.common.rendering.api.ILayoutPullParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -36,14 +37,15 @@ import java.io.Reader; */ public class BridgeXmlBlockParser implements XmlResourceParser { - private XmlPullParser mParser; - private XmlPullAttributes mAttrib; + private final XmlPullParser mParser; + private final XmlPullAttributes mAttrib; + private final BridgeContext mContext; + private final boolean mPlatformFile; private boolean mStarted = false; - private boolean mDecNextDepth = false; - private int mDepth = 0; private int mEventType = START_DOCUMENT; - private final boolean mPlatformFile; + + private boolean mPopped = true; // default to true in case it's not pushed. /** * Builds a {@link BridgeXmlBlockParser}. @@ -53,25 +55,45 @@ public class BridgeXmlBlockParser implements XmlResourceParser { */ public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) { mParser = parser; + mContext = context; mPlatformFile = platformFile; mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile); + + if (mContext != null) { + mContext.pushParser(this); + mPopped = false; + } } - + public boolean isPlatformFile() { return mPlatformFile; } - public Object getViewKey() { - if (mParser instanceof IXmlPullParser) { - return ((IXmlPullParser)mParser).getViewKey(); + public ILayoutPullParser getParser(String layoutName) { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getParser(layoutName); + } + + return null; + } + + public Object getViewCookie() { + if (mParser instanceof ILayoutPullParser) { + return ((ILayoutPullParser)mParser).getViewCookie(); } return null; } - - + + public void ensurePopped() { + if (mContext != null && mPopped == false) { + mContext.popParser(); + mPopped = true; + } + } + // ------- XmlResourceParser implementation - + public void setFeature(String name, boolean state) throws XmlPullParserException { if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { @@ -145,7 +167,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { } public int getDepth() { - return mDepth; + return mParser.getDepth(); } public String getText() { @@ -236,17 +258,10 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return START_DOCUMENT; } int ev = mParser.next(); - if (mDecNextDepth) { - mDepth--; - mDecNextDepth = false; - } - switch (ev) { - case START_TAG: - mDepth++; - break; - case END_TAG: - mDecNextDepth = true; - break; + + if (ev == END_TAG && mParser.getDepth() == 1) { + // done with parser remove it from the context stack. + ensurePopped(); } mEventType = ev; return ev; @@ -301,7 +316,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { // AttributeSet implementation - + public void close() { // pass } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java index d145ff6..ba856e0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlPullAttributes.java @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; -import com.android.layoutlib.api.IResourceValue; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; @@ -55,7 +59,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { String ns = mParser.getAttributeNamespace(index); if (BridgeConstants.NS_RESOURCES.equals(ns)) { - Integer v = Bridge.getResourceValue(BridgeConstants.RES_ATTR, name); + Integer v = Bridge.getResourceId(ResourceType.ATTR, name); if (v != null) { return v.intValue(); } @@ -66,8 +70,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { // this is not an attribute in the android namespace, we query the customviewloader, if // the namespaces match. if (mContext.getProjectCallback().getNamespace().equals(ns)) { - Integer v = mContext.getProjectCallback().getResourceValue(BridgeConstants.RES_ATTR, - name); + Integer v = mContext.getProjectCallback().getResourceId(ResourceType.ATTR, name); if (v != null) { return v.intValue(); } @@ -100,16 +103,17 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { private int resolveResourceValue(String value, int defaultValue) { // now look for this particular value - IResourceValue resource = mContext.resolveResValue( - mContext.findResValue(value, mPlatformFile)); + RenderResources resources = mContext.getRenderResources(); + ResourceValue resource = resources.resolveResValue( + resources.findResValue(value, mPlatformFile)); if (resource != null) { Integer id = null; if (mPlatformFile || resource.isFramework()) { - id = Bridge.getResourceValue(resource.getType(), resource.getName()); + id = Bridge.getResourceId(resource.getResourceType(), resource.getName()); } else { - id = mContext.getProjectCallback().getResourceValue( - resource.getType(), resource.getName()); + id = mContext.getProjectCallback().getResourceId( + resource.getResourceType(), resource.getName()); } if (id != null) { 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 new file mode 100644 index 0000000..0c4b0d3 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.bars; + +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.Density; +import com.android.resources.ResourceType; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Base "bar" class for the window decor around the the edited layout. + * This is basically an horizontal layout that loads a given layout on creation (it is read + * through {@link Class#getResourceAsStream(String)}). + * + * The given layout should be a merge layout so that all the children belong to this class directly. + * + * It also provides a few utility methods to configure the content of the layout. + */ +abstract class CustomBar extends LinearLayout { + + protected abstract TextView getStyleableTextView(); + + protected CustomBar(Context context, Density density, String layoutPath) + throws XmlPullParserException { + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput( + getClass().getResourceAsStream(layoutPath), + "UTF8"); + + BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( + parser, (BridgeContext) context, false /*platformFile*/); + + try { + inflater.inflate(bridgeParser, this, true); + } finally { + bridgeParser.ensurePopped(); + } + } + + private InputStream getIcon(String iconName, Density[] densityInOut, String[] pathOut, + boolean tryOtherDensities) { + // current density + Density density = densityInOut[0]; + + // bitmap url relative to this class + 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*/); + if (stream != null) { + return stream; + } + } + } + } + + return stream; + } + + protected void loadIcon(int index, String iconName, Density density) { + 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, + true /*tryOtherDensities*/); + density = densityInOut[0]; + + if (stream != null) { + // look for a cached bitmap + Bitmap bitmap = Bridge.getCachedBitmap(pathOut[0], true /*isFramework*/); + if (bitmap == null) { + try { + bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); + Bridge.setCachedBitmap(pathOut[0], bitmap, true /*isFramework*/); + } catch (IOException e) { + return; + } + } + + if (bitmap != null) { + BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), + bitmap); + imageView.setBackgroundDrawable(drawable); + } + } + } + } + + protected void loadIcon(int index, String iconReference) { + ResourceValue value = getResourceValue(iconReference); + if (value != null) { + loadIcon(index, value); + } + } + + protected Drawable loadIcon(int index, ResourceType type, String name) { + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + // find the resource + ResourceValue value = res.getFrameworkResource(type, name); + + // resolve it if needed + value = res.resolveResValue(value); + return loadIcon(index, value); + } + + private Drawable loadIcon(int index, ResourceValue value) { + View child = getChildAt(index); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + + Drawable drawable = ResourceHelper.getDrawable( + value, (BridgeContext) mContext); + if (drawable != null) { + imageView.setBackgroundDrawable(drawable); + } + + return drawable; + } + + return null; + } + + protected TextView setText(int index, String stringReference) { + View child = getChildAt(index); + if (child instanceof TextView) { + TextView textView = (TextView) child; + ResourceValue value = getResourceValue(stringReference); + if (value != null) { + textView.setText(value.getValue()); + } else { + textView.setText(stringReference); + } + return textView; + } + + return null; + } + + protected void setStyle(String themeEntryName) { + + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + ResourceValue value = res.findItemInTheme(themeEntryName); + value = res.resolveResValue(value); + + if (value instanceof StyleResourceValue == false) { + return; + } + + StyleResourceValue style = (StyleResourceValue) value; + + // get the background + ResourceValue backgroundValue = res.findItemInStyle(style, "background"); + backgroundValue = res.resolveResValue(backgroundValue); + if (backgroundValue != null) { + Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext); + if (d != null) { + setBackgroundDrawable(d); + } + } + + TextView textView = getStyleableTextView(); + if (textView != null) { + // get the text style + ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle"); + textStyleValue = res.resolveResValue(textStyleValue); + if (textStyleValue instanceof StyleResourceValue) { + StyleResourceValue textStyle = (StyleResourceValue) textStyleValue; + + ResourceValue textSize = res.findItemInStyle(textStyle, "textSize"); + textSize = res.resolveResValue(textSize); + + if (textSize != null) { + TypedValue out = new TypedValue(); + if (ResourceHelper.stringToFloat(textSize.getValue(), out)) { + textView.setTextSize( + out.getDimension(bridgeContext.getResources().mMetrics)); + } + } + + + ResourceValue textColor = res.findItemInStyle(textStyle, "textColor"); + textColor = res.resolveResValue(textColor); + if (textColor != null) { + ColorStateList stateList = ResourceHelper.getColorStateList( + textColor, bridgeContext); + if (stateList != null) { + textView.setTextColor(stateList); + } + } + } + } + } + + private ResourceValue getResourceValue(String reference) { + BridgeContext bridgeContext = (BridgeContext) mContext; + RenderResources res = bridgeContext.getRenderResources(); + + // find the resource + ResourceValue value = res.findResValue(reference, false /*isFramework*/); + + // resolve it if needed + return res.resolveResValue(value); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java new file mode 100644 index 0000000..5507ef9 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/PhoneSystemBar.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.bars; + +import com.android.resources.Density; +import com.android.resources.ResourceType; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LevelListDrawable; +import android.view.Gravity; +import android.widget.TextView; + +public class PhoneSystemBar extends CustomBar { + + public PhoneSystemBar(Context context, Density density) throws XmlPullParserException { + super(context, density, "/bars/phone_system_bar.xml"); + + setGravity(mGravity | Gravity.RIGHT); + setBackgroundColor(0xFF000000); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + // 0 is the spacer. + loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density); + Drawable drawable = loadIcon(2, ResourceType.DRAWABLE, "stat_sys_battery_charge"); + if (drawable instanceof LevelListDrawable) { + ((LevelListDrawable) drawable).setLevel(100); + } + } + + @Override + protected TextView getStyleableTextView() { + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java new file mode 100644 index 0000000..d7401d9 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.bars; + +import com.android.resources.Density; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.widget.TextView; + +public class TitleBar extends CustomBar { + + private TextView mTextView; + + public TitleBar(Context context, Density density, String label) + throws XmlPullParserException { + super(context, density, "/bars/title_bar.xml"); + + // Cannot access the inside items through id because no R.id values have been + // created for them. + // We do know the order though. + mTextView = setText(0, label); + + setStyle("windowTitleBackgroundStyle"); + } + + @Override + protected TextView getStyleableTextView() { + return mTextView; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java new file mode 100644 index 0000000..ae1217d --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import com.android.layoutlib.bridge.util.Debug; +import com.android.layoutlib.bridge.util.SparseWeakArray; + +import android.util.SparseArray; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages native delegates. + * + * This is used in conjunction with layoublib_create: certain Android java classes are mere + * wrappers around a heavily native based implementation, and we need a way to run these classes + * in our Eclipse rendering framework without bringing all the native code from the Android + * platform. + * + * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their + * native methods by "delegate calls". + * + * For example, a native method android.graphics.Matrix.init(...) will actually become + * a call to android.graphics.Matrix_Delegate.init(...). + * + * The Android java classes that use native code uses an int (Java side) to reference native + * objects. This int is generally directly the pointer to the C structure counterpart. + * Typically a creation method will return such an int, and then this int will be passed later + * to a Java method to identify the C object to manipulate. + * + * Since we cannot use the Java object reference as the int directly, DelegateManager manages the + * int -> Delegate class link. + * + * Native methods usually always have the int as parameters. The first thing the delegate method + * will do is call {@link #getDelegate(int)} to get the Java object matching the int. + * + * Typical native init methods are returning a new int back to the Java class, so + * {@link #addNewDelegate(Object)} does the same. + * + * The JNI references are counted, so we do the same through a {@link WeakReference}. Because + * the Java object needs to count as a reference (even though it only holds an int), we use the + * following mechanism: + * + * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(int)} adds and removes + * the delegate to/from a list. This list hold the reference and prevents the GC from reclaiming + * the delegate. + * + * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a + * {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically + * when nothing references it. This means that any class that holds a delegate (except for the + * Java main class) must not use the int but the Delegate class instead. The integers must + * only be used in the API between the main Java class and the Delegate. + * + * @param <T> the delegate class to manage + */ +public final class DelegateManager<T> { + private final Class<T> mClass; + private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>(); + /** list used to store delegates when their main object holds a reference to them. + * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed + * @see #addNewDelegate(Object) + * @see #removeJavaReferenceFor(int) + */ + private final List<T> mJavaReferences = new ArrayList<T>(); + private int mDelegateCounter = 0; + + public DelegateManager(Class<T> theClass) { + mClass = theClass; + } + + /** + * Returns the delegate from the given native int. + * <p> + * If the int is zero, then this will always return null. + * <p> + * If the int is non zero and the delegate is not found, this will throw an assert. + * + * @param native_object the native int. + * @return the delegate or null if not found. + */ + public T getDelegate(int native_object) { + if (native_object > 0) { + T delegate = mDelegates.get(native_object); + + if (Debug.DEBUG) { + if (delegate == null) { + System.out.println("Unknown " + mClass.getSimpleName() + " with int " + + native_object); + } + } + + assert delegate != null; + return delegate; + } + return null; + } + + /** + * Adds a delegate to the manager and returns the native int used to identify it. + * @param newDelegate the delegate to add + * @return a unique native int to identify the delegate + */ + public int addNewDelegate(T newDelegate) { + int native_object = ++mDelegateCounter; + mDelegates.put(native_object, newDelegate); + assert !mJavaReferences.contains(newDelegate); + mJavaReferences.add(newDelegate); + + if (Debug.DEBUG) { + System.out.println("New " + mClass.getSimpleName() + " with int " + native_object); + } + + return native_object; + } + + /** + * Removes the main reference on the given delegate. + * @param native_object the native integer representing the delegate. + */ + public void removeJavaReferenceFor(int native_object) { + T delegate = getDelegate(native_object); + + if (Debug.DEBUG) { + System.out.println("Removing main Java ref on " + mClass.getSimpleName() + + " with int " + native_object); + } + + mJavaReferences.remove(delegate); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java index 801503b..f62fad2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.impl; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -134,6 +134,15 @@ public final class FontLoader { return mFallBackFonts; } + /** + * Returns a {@link Font} object given a family name and a style value (constant in + * {@link Typeface}). + * @param family the family name + * @param style a 1-item array containing the requested style. Based on the font being read + * the actual style may be different. The array contains the actual style after + * the method returns. + * @return the font object or null if no match could be found. + */ public synchronized Font getFont(String family, int[] style) { if (family == null) { return null; @@ -154,7 +163,7 @@ public final class FontLoader { mTtfToFontMap.put(ttf, styleMap); } - Font f = styleMap.get(style); + Font f = styleMap.get(style[0]); if (f != null) { return f; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java new file mode 100644 index 0000000..1cf64a8 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -0,0 +1,796 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint_Delegate; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.Region_Delegate; +import android.graphics.Shader_Delegate; +import android.graphics.Xfermode_Delegate; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +/** + * Class representing a graphics context snapshot, as well as a context stack as a linked list. + * <p> + * This is based on top of {@link Graphics2D} but can operate independently if none are available + * yet when setting transforms and clip information. + * <p> + * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and + * {@link #draw(Drawable, Paint_Delegate)} + * + * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through + * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer} + * for each layer. Doing a save() will duplicate this list so that each graphics2D object + * ({@link Layer#getGraphics()}) is configured only for the new snapshot. + */ +public class GcSnapshot { + + private final GcSnapshot mPrevious; + private final int mFlags; + + /** list of layers. The first item in the list is always the */ + private final ArrayList<Layer> mLayers = new ArrayList<Layer>(); + + /** temp transform in case transformation are set before a Graphics2D exists */ + private AffineTransform mTransform = null; + /** temp clip in case clipping is set before a Graphics2D exists */ + private Area mClip = null; + + // local layer data + /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}. + * If this is null, this does not mean there's no layer, just that the snapshot is not the + * one that created the layer. + * @see #getLayerSnapshot() + */ + private final Layer mLocalLayer; + private final Paint_Delegate mLocalLayerPaint; + private final Rect mLayerBounds; + + public interface Drawable { + void draw(Graphics2D graphics, Paint_Delegate paint); + } + + /** + * Class containing information about a layer. + * + * This contains graphics, bitmap and layer information. + */ + private static class Layer { + private final Graphics2D mGraphics; + private final Bitmap_Delegate mBitmap; + private final BufferedImage mImage; + /** the flags that were used to configure the layer. This is never changed, and passed + * as is when {@link #makeCopy()} is called */ + private final int mFlags; + /** the original content of the layer when the next object was created. This is not + * passed in {@link #makeCopy()} and instead is recreated when a new layer is added + * (depending on its flags) */ + private BufferedImage mOriginalCopy; + + /** + * Creates a layer with a graphics and a bitmap. This is only used to create + * the base layer. + * + * @param graphics the graphics + * @param bitmap the bitmap + */ + Layer(Graphics2D graphics, Bitmap_Delegate bitmap) { + mGraphics = graphics; + mBitmap = bitmap; + mImage = mBitmap.getImage(); + mFlags = 0; + } + + /** + * Creates a layer with a graphics and an image. If the image belongs to a + * {@link Bitmap_Delegate} (case of the base layer), then + * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used. + * + * @param graphics the graphics the new graphics for this layer + * @param image the image the image from which the graphics came + * @param flags the flags that were used to save this layer + */ + Layer(Graphics2D graphics, BufferedImage image, int flags) { + mGraphics = graphics; + mBitmap = null; + mImage = image; + mFlags = flags; + } + + /** The Graphics2D, guaranteed to be non null */ + Graphics2D getGraphics() { + return mGraphics; + } + + /** The BufferedImage, guaranteed to be non null */ + BufferedImage getImage() { + return mImage; + } + + /** Returns the layer save flags. This is only valid for additional layers. + * For the base layer this will always return 0; + * For a given layer, all further copies of this {@link Layer} object in new snapshots + * will always return the same value. + */ + int getFlags() { + return mFlags; + } + + Layer makeCopy() { + if (mBitmap != null) { + return new Layer((Graphics2D) mGraphics.create(), mBitmap); + } + + return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags); + } + + /** sets an optional copy of the original content to be used during restore */ + void setOriginalCopy(BufferedImage image) { + mOriginalCopy = image; + } + + BufferedImage getOriginalCopy() { + return mOriginalCopy; + } + + /** + * Sets the clip for the graphics2D object associated with the layer. + * This should be used over the normal Graphics2D setClip method. + * + * @param clipShape the shape to use a the clip shape. + */ + void setClip(Shape clipShape) { + // because setClip is only guaranteed to work with rectangle shape, + // first reset the clip to max and then intersect the current (empty) + // clip with the shap. + mGraphics.setClip(null); + mGraphics.clip(clipShape); + } + + /** + * Clips the layer with the given shape. This performs an intersect between the current + * clip shape and the given shape. + * @param shape the new clip shape. + */ + public void clip(Shape shape) { + mGraphics.clip(shape); + } + } + + /** + * Creates the root snapshot associating it with a given bitmap. + * <p> + * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be + * called before the snapshot can be used to draw. Transform and clip operations are permitted + * before. + * + * @param image the image to associate to the snapshot or null. + * @return the root snapshot + */ + public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { + GcSnapshot snapshot = new GcSnapshot(); + if (bitmap != null) { + snapshot.setBitmap(bitmap); + } + + return snapshot; + } + + /** + * Saves the current state according to the given flags and returns the new current snapshot. + * <p/> + * This is the equivalent of {@link Canvas#save(int)} + * + * @param flags the save flags. + * @return the new snapshot + * + * @see Canvas#save(int) + */ + public GcSnapshot save(int flags) { + return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags); + } + + /** + * Saves the current state and creates a new layer, and returns the new current snapshot. + * <p/> + * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)} + * + * @param layerBounds the layer bounds + * @param paint the Paint information used to blit the layer back into the layers underneath + * upon restore + * @param flags the save flags. + * @return the new snapshot + * + * @see Canvas#saveLayer(RectF, Paint, int) + */ + public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) { + return new GcSnapshot(this, layerBounds, paint, flags); + } + + /** + * Creates the root snapshot. + * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible. + */ + private GcSnapshot() { + mPrevious = null; + mFlags = 0; + mLocalLayer = null; + mLocalLayerPaint = null; + mLayerBounds = null; + } + + /** + * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored + * into the main graphics when {@link #restore()} is called. + * + * @param previous the previous snapshot head. + * @param layerBounds the region of the layer. Optional, if null, this is a normal save() + * @param paint the Paint information used to blit the layer back into the layers underneath + * upon restore + * @param flags the flags regarding what should be saved. + */ + private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) { + assert previous != null; + mPrevious = previous; + mFlags = flags; + + // make a copy of the current layers before adding the new one. + // This keeps the same BufferedImage reference but creates new Graphics2D for this + // snapshot. + // It does not copy whatever original copy the layers have, as they will be done + // only if the new layer doesn't clip drawing to itself. + for (Layer layer : mPrevious.mLayers) { + mLayers.add(layer.makeCopy()); + } + + if (layerBounds != null) { + // get the current transform + AffineTransform matrix = mLayers.get(0).getGraphics().getTransform(); + + // transform the layerBounds with the current transform and stores it into a int rect + RectF rect2 = new RectF(); + mapRect(matrix, rect2, layerBounds); + mLayerBounds = new Rect(); + rect2.round(mLayerBounds); + + // get the base layer (always at index 0) + Layer baseLayer = mLayers.get(0); + + // create the image for the layer + BufferedImage layerImage = new BufferedImage( + baseLayer.getImage().getWidth(), + baseLayer.getImage().getHeight(), + (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ? + BufferedImage.TYPE_INT_ARGB : + BufferedImage.TYPE_INT_RGB); + + // create a graphics for it so that drawing can be done. + Graphics2D layerGraphics = layerImage.createGraphics(); + + // because this layer inherits the current context for transform and clip, + // set them to one from the base layer. + AffineTransform currentMtx = baseLayer.getGraphics().getTransform(); + layerGraphics.setTransform(currentMtx); + + // create a new layer for this new layer and add it to the list at the end. + mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags)); + + // set the clip on it. + Shape currentClip = baseLayer.getGraphics().getClip(); + mLocalLayer.setClip(currentClip); + + // if the drawing is not clipped to the local layer only, we save the current content + // of all other layers. We are only interested in the part that will actually + // be drawn, so we create as small bitmaps as we can. + // This is so that we can erase the drawing that goes in the layers below that will + // be coming from the layer itself. + if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) { + int w = mLayerBounds.width(); + int h = mLayerBounds.height(); + for (int i = 0 ; i < mLayers.size() - 1 ; i++) { + Layer layer = mLayers.get(i); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + graphics.drawImage(layer.getImage(), + 0, 0, w, h, + mLayerBounds.left, mLayerBounds.top, + mLayerBounds.right, mLayerBounds.bottom, + null); + graphics.dispose(); + layer.setOriginalCopy(image); + } + } + } else { + mLocalLayer = null; + mLayerBounds = null; + } + + mLocalLayerPaint = paint; + } + + public void dispose() { + for (Layer layer : mLayers) { + layer.getGraphics().dispose(); + } + + if (mPrevious != null) { + mPrevious.dispose(); + } + } + + /** + * Restores the top {@link GcSnapshot}, and returns the next one. + */ + public GcSnapshot restore() { + return doRestore(); + } + + /** + * Restores the {@link GcSnapshot} to <var>saveCount</var>. + * @param saveCount the saveCount or -1 to only restore 1. + * + * @return the new head of the Gc snapshot stack. + */ + public GcSnapshot restoreTo(int saveCount) { + return doRestoreTo(size(), saveCount); + } + + public int size() { + if (mPrevious != null) { + return mPrevious.size() + 1; + } + + return 1; + } + + /** + * Link the snapshot to a Bitmap_Delegate. + * <p/> + * This is only for the case where the snapshot was created with a null image when calling + * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to + * a previous snapshot. + * <p/> + * If any transform or clip information was set before, they are put into the Graphics object. + * @param bitmap the bitmap to link to. + */ + public void setBitmap(Bitmap_Delegate bitmap) { + // create a new Layer for the bitmap. This will be the base layer. + Graphics2D graphics2D = bitmap.getImage().createGraphics(); + Layer baseLayer = new Layer(graphics2D, bitmap); + + // Set the current transform and clip which can either come from mTransform/mClip if they + // were set when there was no bitmap/layers or from the current base layers if there is + // one already. + + graphics2D.setTransform(getTransform()); + // reset mTransform in case there was one. + mTransform = null; + + baseLayer.setClip(getClip()); + // reset mClip in case there was one. + mClip = null; + + // replace whatever current layers we have with this. + mLayers.clear(); + mLayers.add(baseLayer); + + } + + public void translate(float dx, float dy) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().translate(dx, dy); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.translate(dx, dy); + } + } + + public void rotate(double radians) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().rotate(radians); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.rotate(radians); + } + } + + public void scale(float sx, float sy) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().scale(sx, sy); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.scale(sx, sy); + } + } + + public AffineTransform getTransform() { + if (mLayers.size() > 0) { + // all graphics2D in the list have the same transform + return mLayers.get(0).getGraphics().getTransform(); + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + return mTransform; + } + } + + public void setTransform(AffineTransform transform) { + if (mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.getGraphics().setTransform(transform); + } + } else { + if (mTransform == null) { + mTransform = new AffineTransform(); + } + mTransform.setTransform(transform); + } + } + + public boolean clip(Shape shape, int regionOp) { + // Simple case of intersect with existing layers. + // Because Graphics2D#setClip works a bit peculiarly, we optimize + // the case of clipping by intersection, as it's supported natively. + if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) { + for (Layer layer : mLayers) { + layer.clip(shape); + } + + Shape currentClip = getClip(); + return currentClip != null && currentClip.getBounds().isEmpty() == false; + } + + Area area = null; + + if (regionOp == Region.Op.REPLACE.nativeInt) { + area = new Area(shape); + } else { + area = Region_Delegate.combineShapes(getClip(), shape, regionOp); + } + + assert area != null; + + if (mLayers.size() > 0) { + if (area != null) { + for (Layer layer : mLayers) { + layer.setClip(area); + } + } + + Shape currentClip = getClip(); + return currentClip != null && currentClip.getBounds().isEmpty() == false; + } else { + if (area != null) { + mClip = area; + } else { + mClip = new Area(); + } + + return mClip.getBounds().isEmpty() == false; + } + } + + public boolean clipRect(float left, float top, float right, float bottom, int regionOp) { + return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp); + } + + /** + * Returns the current clip, or null if none have been setup. + */ + public Shape getClip() { + if (mLayers.size() > 0) { + // they all have the same clip + return mLayers.get(0).getGraphics().getClip(); + } else { + return mClip; + } + } + + private GcSnapshot doRestoreTo(int size, int saveCount) { + if (size <= saveCount) { + return this; + } + + // restore the current one first. + GcSnapshot previous = doRestore(); + + if (size == saveCount + 1) { // this was the only one that needed restore. + return previous; + } else { + return previous.doRestoreTo(size - 1, saveCount); + } + } + + /** + * Executes the Drawable's draw method, with a null paint delegate. + * <p/> + * Note that the method can be called several times if there are more than one active layer. + * @param drawable + */ + public void draw(Drawable drawable) { + draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/); + } + + /** + * Executes the Drawable's draw method. + * <p/> + * Note that the method can be called several times if there are more than one active layer. + * @param drawable + * @param paint + * @param compositeOnly whether the paint is used for composite only. This is typically + * the case for bitmaps. + * @param forceSrcMode if true, this overrides the composite to be SRC + */ + public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, + boolean forceSrcMode) { + // the current snapshot may not have a mLocalLayer (ie it was created on save() instead + // of saveLayer(), but that doesn't mean there's no layer. + // mLayers however saves all the information we need (flags). + if (mLayers.size() == 1) { + // no layer, only base layer. easy case. + drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode); + } else { + // draw in all the layers until the layer save flags tells us to stop (ie drawing + // in that layer is limited to the layer itself. + int flags; + int i = mLayers.size() - 1; + + do { + Layer layer = mLayers.get(i); + + drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode); + + // then go to previous layer, only if there are any left, and its flags + // doesn't restrict drawing to the layer itself. + i--; + flags = layer.getFlags(); + } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); + } + } + + private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, + boolean compositeOnly, boolean forceSrcMode) { + Graphics2D originalGraphics = layer.getGraphics(); + // get a Graphics2D object configured with the drawing parameters. + Graphics2D configuredGraphics2D = + paint != null ? + createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) : + (Graphics2D) originalGraphics.create(); + + try { + drawable.draw(configuredGraphics2D, paint); + } finally { + // dispose Graphics2D object + configuredGraphics2D.dispose(); + } + } + + private GcSnapshot doRestore() { + if (mPrevious != null) { + if (mLocalLayer != null) { + // prepare to blit the layers in which we have draw, in the layer beneath + // them, starting with the top one (which is the current local layer). + int i = mLayers.size() - 1; + int flags; + do { + Layer dstLayer = mLayers.get(i - 1); + + restoreLayer(dstLayer); + + flags = dstLayer.getFlags(); + i--; + } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0); + } + + // if this snapshot does not save everything, then set the previous snapshot + // to this snapshot content + + // didn't save the matrix? set the current matrix on the previous snapshot + if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) { + AffineTransform mtx = getTransform(); + for (Layer layer : mPrevious.mLayers) { + layer.getGraphics().setTransform(mtx); + } + } + + // didn't save the clip? set the current clip on the previous snapshot + if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) { + Shape clip = getClip(); + for (Layer layer : mPrevious.mLayers) { + layer.setClip(clip); + } + } + } + + for (Layer layer : mLayers) { + layer.getGraphics().dispose(); + } + + return mPrevious; + } + + private void restoreLayer(Layer dstLayer) { + + Graphics2D baseGfx = dstLayer.getImage().createGraphics(); + + // if the layer contains an original copy this means the flags + // didn't restrict drawing to the local layer and we need to make sure the + // layer bounds in the layer beneath didn't receive any drawing. + // so we use the originalCopy to erase the new drawings in there. + BufferedImage originalCopy = dstLayer.getOriginalCopy(); + if (originalCopy != null) { + Graphics2D g = (Graphics2D) baseGfx.create(); + g.setComposite(AlphaComposite.Src); + + g.drawImage(originalCopy, + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + 0, 0, mLayerBounds.width(), mLayerBounds.height(), + null); + g.dispose(); + } + + // now draw put the content of the local layer onto the layer, + // using the paint information + Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, + true /*alphaOnly*/, false /*forceSrcMode*/); + + g.drawImage(mLocalLayer.getImage(), + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, + null); + g.dispose(); + + baseGfx.dispose(); + } + + /** + * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. + * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. + */ + private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, + boolean compositeOnly, boolean forceSrcMode) { + // make new one graphics + Graphics2D g = (Graphics2D) original.create(); + + // configure it + + if (paint.isAntiAliased()) { + g.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + + boolean customShader = false; + + // get the shader first, as it'll replace the color if it can be used it. + if (compositeOnly == false) { + Shader_Delegate shaderDelegate = paint.getShader(); + if (shaderDelegate != null) { + if (shaderDelegate.isSupported()) { + java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); + assert shaderPaint != null; + if (shaderPaint != null) { + g.setPaint(shaderPaint); + customShader = true; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, + shaderDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); + } + } + + // if no shader, use the paint color + if (customShader == false) { + g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); + } + + // set the stroke + g.setStroke(paint.getJavaStroke()); + } + + // the alpha for the composite. Always opaque if the normal paint color is used since + // it contains the alpha + int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; + + if (forceSrcMode) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC, (float) alpha / 255.f)); + } else { + boolean customXfermode = false; + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate != null) { + if (xfermodeDelegate.isSupported()) { + Composite composite = xfermodeDelegate.getComposite(alpha); + assert composite != null; + if (composite != null) { + g.setComposite(composite); + customXfermode = true; + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, + xfermodeDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); + } + } + + // if there was no custom xfermode, but we have alpha (due to a shader and a non + // opaque alpha channel in the paint color), then we create an AlphaComposite anyway + // that will handle the alpha. + if (customXfermode == false && alpha != 0xFF) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } + } + + return g; + } + + private void mapRect(AffineTransform matrix, RectF dst, RectF src) { + // array with 4 corners + float[] corners = new float[] { + src.left, src.top, + src.right, src.top, + src.right, src.bottom, + src.left, src.bottom, + }; + + // apply the transform to them. + matrix.transform(corners, 0, corners, 0, 4); + + // now put the result in the rect. We take the min/max of Xs and min/max of Ys + dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6])); + dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6])); + + dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7])); + dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7])); + } + +} 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 new file mode 100644 index 0000000..8e80c21 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.resources.ResourceType; + +import android.util.DisplayMetrics; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Base class for rendering action. + * + * It provides life-cycle methods to init and stop the rendering. + * The most important methods are: + * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()} + * after the rendering. + * + * + * @param <T> the {@link RenderParams} implementation + * + */ +public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider { + + /** + * The current context being rendered. This is set through {@link #acquire(long)} and + * {@link #init(long)}, and unset in {@link #release()}. + */ + private static BridgeContext sCurrentContext = null; + + private final T mParams; + + private BridgeContext mContext; + + /** + * Creates a renderAction. + * <p> + * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a + * call to {@link RenderAction#acquire(long)} + * + * @param params the RenderParams. This must be a copy that the action can keep + * + */ + protected RenderAction(T params) { + mParams = params; + } + + /** + * Initializes and acquires the scene, creating various Android objects such as context, + * inflater, and parser. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #acquire(long) + * @see #release() + */ + public Result init(long timeout) { + // acquire the lock. if the result is null, lock was just acquired, otherwise, return + // the result. + Result result = acquireLock(timeout); + if (result != null) { + return result; + } + + // setup the display Metrics. + DisplayMetrics metrics = new DisplayMetrics(); + metrics.densityDpi = mParams.getDensity().getDpiValue(); + metrics.density = metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT; + metrics.scaledDensity = metrics.density; + metrics.widthPixels = mParams.getScreenWidth(); + metrics.heightPixels = mParams.getScreenHeight(); + metrics.xdpi = mParams.getXdpi(); + metrics.ydpi = mParams.getYdpi(); + + RenderResources resources = mParams.getResources(); + + // build the context + mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, + mParams.getProjectCallback(), mParams.getTargetSdkVersion()); + + setUp(); + + return SUCCESS.createResult(); + } + + /** + * Prepares the scene for action. + * <p> + * This call is blocking if another rendering/inflating is currently happening, and will return + * whether the preparation worked. + * + * The preparation can fail if another rendering took too long and the timeout was elapsed. + * + * More than one call to this from the same thread will have no effect and will return + * {@link Result#SUCCESS}. + * + * After scene actions have taken place, only one call to {@link #release()} must be + * done. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #release() + * + * @throws IllegalStateException if {@link #init(long)} was never called. + */ + public Result acquire(long timeout) { + if (mContext == null) { + throw new IllegalStateException("After scene creation, #init() must be called"); + } + + // acquire the lock. if the result is null, lock was just acquired, otherwise, return + // the result. + Result result = acquireLock(timeout); + if (result != null) { + return result; + } + + setUp(); + + return SUCCESS.createResult(); + } + + /** + * Acquire the lock so that the scene can be acted upon. + * <p> + * This returns null if the lock was just acquired, otherwise it returns + * {@link Result#SUCCESS} if the lock already belonged to that thread, or another + * instance (see {@link Result#getStatus()}) if an error occurred. + * + * @param timeout the time to wait if another rendering is happening. + * @return null if the lock was just acquire or another result depending on the state. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene. + */ + private Result acquireLock(long timeout) { + ReentrantLock lock = Bridge.getLock(); + if (lock.isHeldByCurrentThread() == false) { + try { + boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); + + if (acquired == false) { + return ERROR_TIMEOUT.createResult(); + } + } catch (InterruptedException e) { + return ERROR_LOCK_INTERRUPTED.createResult(); + } + } else { + // This thread holds the lock already. Checks that this wasn't for a different context. + // If this is called by init, mContext will be null and so should sCurrentContext + // anyway + if (mContext != sCurrentContext) { + throw new IllegalStateException("Acquiring different scenes from same thread without releases"); + } + return SUCCESS.createResult(); + } + + return null; + } + + /** + * Cleans up the scene after an action. + */ + public void release() { + ReentrantLock lock = Bridge.getLock(); + + // with the use of finally blocks, it is possible to find ourself calling this + // without a successful call to prepareScene. This test makes sure that unlock() will + // not throw IllegalMonitorStateException. + if (lock.isHeldByCurrentThread()) { + tearDown(); + lock.unlock(); + } + } + + /** + * Sets up the session for rendering. + * <p/> + * The counterpart is {@link #tearDown()}. + */ + private void setUp() { + // make sure the Resources object references the context (and other objects) for this + // scene + mContext.initResources(); + sCurrentContext = mContext; + + LayoutLog currentLog = mParams.getLog(); + Bridge.setLog(currentLog); + mContext.getRenderResources().setFrameworkResourceIdProvider(this); + mContext.getRenderResources().setLogger(currentLog); + } + + /** + * Tear down the session after rendering. + * <p/> + * The counterpart is {@link #setUp()}. + */ + private void tearDown() { + // Make sure to remove static references, otherwise we could not unload the lib + mContext.disposeResources(); + sCurrentContext = null; + + Bridge.setLog(null); + mContext.getRenderResources().setFrameworkResourceIdProvider(null); + mContext.getRenderResources().setLogger(null); + } + + public static BridgeContext getCurrentContext() { + return sCurrentContext; + } + + protected T getParams() { + return mParams; + } + + protected BridgeContext getContext() { + return mContext; + } + + /** + * Returns the log associated with the session. + * @return the log or null if there are none. + */ + public LayoutLog getLog() { + if (mParams != null) { + return mParams.getLog(); + } + + return null; + } + + /** + * Checks that the lock is owned by the current thread and that the current context is the one + * from this scene. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + */ + protected void checkLock() { + ReentrantLock lock = Bridge.getLock(); + if (lock.isHeldByCurrentThread() == false) { + throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); + } + if (sCurrentContext != mContext) { + throw new IllegalStateException("Thread acquired a scene but is rendering a different one"); + } + } + + // --- FrameworkResourceIdProvider methods + + @Override + public Integer getId(ResourceType resType, String resName) { + return Bridge.getResourceId(resType, resName); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java new file mode 100644 index 0000000..10a4368 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; + +import com.android.ide.common.rendering.api.DrawableParams; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeWindow; +import com.android.layoutlib.bridge.android.BridgeWindowSession; +import com.android.resources.ResourceType; + +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.view.View; +import android.view.View.AttachInfo; +import android.view.View.MeasureSpec; +import android.widget.FrameLayout; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}. + * + * The class only provides a simple {@link #render()} method, but the full life-cycle of the + * action must be respected. + * + * @see RenderAction + * + */ +public class RenderDrawable extends RenderAction<DrawableParams> { + + public RenderDrawable(DrawableParams params) { + super(new DrawableParams(params)); + } + + public Result render() { + checkLock(); + try { + // get the drawable resource value + DrawableParams params = getParams(); + ResourceValue drawableResource = params.getDrawable(); + + // resolve it + BridgeContext context = getContext(); + drawableResource = context.getRenderResources().resolveResValue(drawableResource); + + if (drawableResource == null || + drawableResource.getResourceType() != ResourceType.DRAWABLE) { + return Status.ERROR_NOT_A_DRAWABLE.createResult(); + } + + // create a simple FrameLayout + FrameLayout content = new FrameLayout(context); + + // get the actual Drawable object to draw + Drawable d = ResourceHelper.getDrawable(drawableResource, context); + content.setBackgroundDrawable(d); + + // set the AttachInfo on the root view. + AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), + new Handler(), null); + info.mHasWindowFocus = true; + info.mWindowVisibility = View.VISIBLE; + info.mInTouchMode = false; // this is so that we can display selections. + content.dispatchAttachedToWindow(info, 0); + + + // measure + int w = params.getScreenWidth(); + int h = params.getScreenHeight(); + int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY); + int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY); + content.measure(w_spec, h_spec); + + // now do the layout. + content.layout(0, 0, w, h); + + // preDraw setup + content.mAttachInfo.mTreeObserver.dispatchOnPreDraw(); + + // draw into a new image + BufferedImage image = getImage(w, h); + + // create an Android bitmap around the BufferedImage + Bitmap bitmap = Bitmap_Delegate.createBitmap(image, + true /*isMutable*/, params.getDensity()); + + // create a Canvas around the Android bitmap + Canvas canvas = new Canvas(bitmap); + canvas.setDensity(params.getDensity().getDpiValue()); + + // and draw + content.draw(canvas); + + return Status.SUCCESS.createResult(image); + } catch (IOException e) { + return ERROR_UNKNOWN.createResult(e.getMessage(), e); + } + } + + protected BufferedImage getImage(int w, int h) { + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D gc = image.createGraphics(); + gc.setComposite(AlphaComposite.Src); + + gc.setColor(new Color(0x00000000, true)); + gc.fillRect(0, 0, w, h); + + // done + gc.dispose(); + + return image; + } + +} 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 new file mode 100644 index 0000000..cfc047f --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -0,0 +1,1007 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + +import com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.RenderParams; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.internal.util.XmlUtils; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeInflater; +import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; +import com.android.layoutlib.bridge.android.BridgeWindow; +import com.android.layoutlib.bridge.android.BridgeWindowSession; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.bars.PhoneSystemBar; +import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.resources.ResourceType; +import com.android.resources.ScreenSize; +import com.android.util.Pair; + +import org.xmlpull.v1.XmlPullParserException; + +import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.AttachInfo; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TabHost.TabSpec; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Class implementing the render session. + * + * A session is a stateful representation of a layout file. It is initialized with data coming + * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then + * be done on the layout. + * + */ +public class RenderSessionImpl extends RenderAction<SessionParams> { + + private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; + private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; + + // scene state + private RenderSession mScene; + private BridgeXmlBlockParser mBlockParser; + private BridgeInflater mInflater; + private ResourceValue mWindowBackground; + private ViewGroup mViewRoot; + private FrameLayout mContentRoot; + private Canvas mCanvas; + private int mMeasuredScreenWidth = -1; + private int mMeasuredScreenHeight = -1; + private boolean mIsAlphaChannelImage; + private boolean mWindowIsFloating; + + private int mStatusBarSize; + private int mTitleBarSize; + + + // information being returned through the API + private BufferedImage mImage; + private List<ViewInfo> mViewInfoList; + + private static final class PostInflateException extends Exception { + private static final long serialVersionUID = 1L; + + public PostInflateException(String message) { + super(message); + } + } + + /** + * Creates a layout scene with all the information coming from the layout bridge API. + * <p> + * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a + * call to {@link RenderSessionImpl#acquire(long)} + * + * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) + */ + public RenderSessionImpl(SessionParams params) { + super(new SessionParams(params)); + } + + /** + * Initializes and acquires the scene, creating various Android objects such as context, + * inflater, and parser. + * + * @param timeout the time to wait if another rendering is happening. + * + * @return whether the scene was prepared + * + * @see #acquire(long) + * @see #release() + */ + @Override + public Result init(long timeout) { + Result result = super.init(timeout); + if (result.isSuccess() == false) { + return result; + } + + SessionParams params = getParams(); + BridgeContext context = getContext(); + + RenderResources resources = getParams().getResources(); + DisplayMetrics metrics = getContext().getMetrics(); + + // use default of true in case it's not found to use alpha by default + mIsAlphaChannelImage = getBooleanThemeValue(resources, + "windowIsFloating", true /*defaultValue*/); + + mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", + true /*defaultValue*/); + + findBackground(resources); + findStatusBar(resources, metrics); + findTitleBar(resources, metrics); + + // build the inflater and parser. + mInflater = new BridgeInflater(context, params.getProjectCallback()); + context.setBridgeInflater(mInflater); + + mBlockParser = new BridgeXmlBlockParser( + params.getLayoutDescription(), context, false /* platformResourceFlag */); + + return SUCCESS.createResult(); + } + + /** + * Inflates the layout. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #init(long)} was not called. + */ + public Result inflate() { + checkLock(); + + try { + + SessionParams params = getParams(); + BridgeContext context = getContext(); + + // the view group that receives the window background. + ViewGroup backgroundView = null; + + if (mWindowIsFloating || params.isForceNoDecor()) { + backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); + } else { + /* + * we're creating the following layout + * + +-------------------------------------------------+ + | System bar | + +-------------------------------------------------+ + | (Layout with background drawable) | + | +---------------------------------------------+ | + | | Title (optional) | | + | +---------------------------------------------+ | + | | Content, vertical extending | | + | | | | + | +---------------------------------------------+ | + +-------------------------------------------------+ + + */ + + LinearLayout topLayout = new LinearLayout(context); + mViewRoot = topLayout; + topLayout.setOrientation(LinearLayout.VERTICAL); + + if (mStatusBarSize > 0) { + // system bar + try { + PhoneSystemBar systemBar = new PhoneSystemBar(context, + params.getDensity()); + systemBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mStatusBarSize)); + topLayout.addView(systemBar); + } catch (XmlPullParserException e) { + + } + } + + LinearLayout backgroundLayout = new LinearLayout(context); + backgroundView = backgroundLayout; + backgroundLayout.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + layoutParams.weight = 1; + backgroundLayout.setLayoutParams(layoutParams); + topLayout.addView(backgroundLayout); + + + // if the theme says no title, then the size will be 0 + if (mTitleBarSize > 0) { + try { + TitleBar titleBar = new TitleBar(context, + params.getDensity(), params.getAppLabel()); + titleBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mTitleBarSize)); + backgroundLayout.addView(titleBar); + } catch (XmlPullParserException e) { + + } + } + + // content frame + mContentRoot = new FrameLayout(context); + layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + layoutParams.weight = 1; + mContentRoot.setLayoutParams(layoutParams); + backgroundLayout.addView(mContentRoot); + } + + + View view = mInflater.inflate(mBlockParser, mContentRoot); + + // set the AttachInfo on the root view. + AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), + new Handler(), null); + info.mHasWindowFocus = true; + info.mWindowVisibility = View.VISIBLE; + info.mInTouchMode = false; // this is so that we can display selections. + mViewRoot.dispatchAttachedToWindow(info, 0); + + // post-inflate process. For now this supports TabHost/TabWidget + postInflateProcess(view, params.getProjectCallback()); + + // get the background drawable + if (mWindowBackground != null && backgroundView != null) { + Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); + backgroundView.setBackgroundDrawable(d); + } + + return SUCCESS.createResult(); + } catch (PostInflateException e) { + return ERROR_INFLATION.createResult(e.getMessage(), e); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + return ERROR_INFLATION.createResult(t.getMessage(), t); + } + } + + /** + * Renders the scene. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @param freshRender whether the render is a new one and should erase the existing bitmap (in + * the case where bitmaps are reused). This is typically needed when not playing + * animations.) + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderParams#getRenderingMode() + * @see RenderSession#render(long) + */ + public Result render(boolean freshRender) { + checkLock(); + + SessionParams params = getParams(); + + try { + if (mViewRoot == null) { + return ERROR_NOT_INFLATED.createResult(); + } + // measure the views + int w_spec, h_spec; + + RenderingMode renderingMode = params.getRenderingMode(); + + // only do the screen measure when needed. + boolean newRenderSize = false; + if (mMeasuredScreenWidth == -1) { + newRenderSize = true; + mMeasuredScreenWidth = params.getScreenWidth(); + mMeasuredScreenHeight = params.getScreenHeight(); + + if (renderingMode != RenderingMode.NORMAL) { + // measure the full size needed by the layout. + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, + renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight, + renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + if (renderingMode.isHorizExpand()) { + int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); + if (neededWidth > mMeasuredScreenWidth) { + mMeasuredScreenWidth = neededWidth; + } + } + + if (renderingMode.isVertExpand()) { + int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); + if (neededHeight > mMeasuredScreenHeight) { + mMeasuredScreenHeight = neededHeight; + } + } + } + } + + // remeasure with the size we need + // This must always be done before the call to layout + w_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenWidth, MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(mMeasuredScreenHeight, MeasureSpec.EXACTLY); + mViewRoot.measure(w_spec, h_spec); + + // now do the layout. + mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + + if (params.isLayoutOnly()) { + // delete the canvas and image to reset them on the next full rendering + mImage = null; + mCanvas = null; + } else { + mViewRoot.mAttachInfo.mTreeObserver.dispatchOnPreDraw(); + + // draw the views + // create the BufferedImage into which the layout will be rendered. + boolean newImage = false; + if (newRenderSize || mCanvas == null) { + if (params.getImageFactory() != null) { + mImage = params.getImageFactory().getImage( + mMeasuredScreenWidth, + mMeasuredScreenHeight); + } else { + mImage = new BufferedImage( + mMeasuredScreenWidth, + mMeasuredScreenHeight, + BufferedImage.TYPE_INT_ARGB); + newImage = true; + } + + if (params.isBgColorOverridden()) { + // since we override the content, it's the same as if it was a new image. + newImage = true; + Graphics2D gc = mImage.createGraphics(); + gc.setColor(new Color(params.getOverrideBgColor(), true)); + gc.setComposite(AlphaComposite.Src); + gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); + gc.dispose(); + } + + // create an Android bitmap around the BufferedImage + Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, + true /*isMutable*/, params.getDensity()); + + // create a Canvas around the Android bitmap + mCanvas = new Canvas(bitmap); + mCanvas.setDensity(params.getDensity().getDpiValue()); + } + + if (freshRender && newImage == false) { + Graphics2D gc = mImage.createGraphics(); + gc.setComposite(AlphaComposite.Src); + + gc.setColor(new Color(0x00000000, true)); + gc.fillRect(0, 0, + mMeasuredScreenWidth, mMeasuredScreenHeight); + + // done + gc.dispose(); + } + + mViewRoot.draw(mCanvas); + } + + mViewInfoList = startVisitingViews(mViewRoot, 0); + + // success! + return SUCCESS.createResult(); + } catch (Throwable e) { + // get the real cause of the exception. + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + + return ERROR_UNKNOWN.createResult(t.getMessage(), t); + } + } + + /** + * Insert a new child into an existing parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener) + */ + public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml, + final int index, final IAnimationListener listener) { + checkLock(); + + BridgeContext context = getContext(); + + // create a block parser for the XML + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + childXml, context, false /* platformResourceFlag */); + + // inflate the child without adding it to the root since we want to control where it'll + // get added. We do pass the parentView however to ensure that the layoutParams will + // be created correctly. + final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); + blockParser.ensurePopped(); + + invalidateRenderingSize(); + + if (listener != null) { + // there is no support for animating views in this API level, so we fake the animation + // through a no animation thread. + new Thread("not animated insertChild") { + @Override + public void run() { + Result result = addView(parentView, child, index); + if (result.isSuccess() == false) { + listener.done(result); + } + + // ready to do the work, acquire the scene. + result = acquire(250); + if (result.isSuccess() == false) { + listener.done(result); + return; + } + + try { + result = render(false /*freshRender*/); + if (result.isSuccess()) { + listener.onNewFrame(RenderSessionImpl.this.getSession()); + } + } finally { + release(); + } + + listener.done(result); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(child); + } + + // add it to the parentView in the correct location + Result result = addView(parentView, child, index); + if (result.isSuccess() == false) { + return result; + } + + result = render(false /*freshRender*/); + if (result.isSuccess()) { + result = result.getCopyWithData(child); + } + + return result; + } + + /** + * Adds a given view to a given parent at a given index. + * + * @param parent the parent to receive the view + * @param view the view to add to the parent + * @param index the index where to do the add. + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result addView(ViewGroup parent, View view, int index) { + try { + parent.addView(view, index); + return SUCCESS.createResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + /** + * Moves a view to a new parent at a given location + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener) + */ + public Result moveChild(final ViewGroup newParentView, final View childView, final int index, + Map<String, String> layoutParamsMap, final IAnimationListener listener) { + checkLock(); + + invalidateRenderingSize(); + + LayoutParams layoutParams = null; + if (layoutParamsMap != null) { + // need to create a new LayoutParams object for the new parent. + layoutParams = newParentView.generateLayoutParams( + new BridgeLayoutParamsMapAttributes(layoutParamsMap)); + } + + // get the current parent of the view that needs to be moved. + final ViewGroup previousParent = (ViewGroup) childView.getParent(); + + if (listener != null) { + final LayoutParams params = layoutParams; + + // there is no support for animating views in this API level, so we fake the animation + // through a no animation thread. + new Thread("not animated moveChild") { + @Override + public void run() { + Result result = moveView(previousParent, newParentView, childView, index, + params); + if (result.isSuccess() == false) { + listener.done(result); + } + + // ready to do the work, acquire the scene. + result = acquire(250); + if (result.isSuccess() == false) { + listener.done(result); + return; + } + + try { + result = render(false /*freshRender*/); + if (result.isSuccess()) { + listener.onNewFrame(RenderSessionImpl.this.getSession()); + } + } finally { + release(); + } + + listener.done(result); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(layoutParams); + } + + Result result = moveView(previousParent, newParentView, childView, index, layoutParams); + if (result.isSuccess() == false) { + return result; + } + + result = render(false /*freshRender*/); + if (layoutParams != null && result.isSuccess()) { + result = result.getCopyWithData(layoutParams); + } + + return result; + } + + /** + * Moves a View from its current parent to a new given parent at a new given location, with + * an optional new {@link LayoutParams} instance + * + * @param previousParent the previous parent, still owning the child at the time of the call. + * @param newParent the new parent + * @param movedView the view to move + * @param index the new location in the new parent + * @param params an option (can be null) {@link LayoutParams} instance. + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result moveView(ViewGroup previousParent, final ViewGroup newParent, + final View movedView, final int index, final LayoutParams params) { + try { + // standard code with no animation. pretty simple. + previousParent.removeView(movedView); + + // add it to the parentView in the correct location + if (params != null) { + newParent.addView(movedView, index, params); + } else { + newParent.addView(movedView, index); + } + + return SUCCESS.createResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + /** + * Removes a child from its current parent. + * <p> + * {@link #acquire(long)} must have been called before this. + * + * @throws IllegalStateException if the current context is different than the one owned by + * the scene, or if {@link #acquire(long)} was not called. + * + * @see RenderSession#removeChild(Object, IAnimationListener) + */ + public Result removeChild(final View childView, final IAnimationListener listener) { + checkLock(); + + invalidateRenderingSize(); + + final ViewGroup parent = (ViewGroup) childView.getParent(); + + if (listener != null) { + // there is no support for animating views in this API level, so we fake the animation + // through a no animation thread. + new Thread("not animated moveChild") { + @Override + public void run() { + Result result = removeView(parent, childView); + if (result.isSuccess() == false) { + listener.done(result); + } + + // ready to do the work, acquire the scene. + result = acquire(250); + if (result.isSuccess() == false) { + listener.done(result); + return; + } + + try { + result = render(false /*freshRender*/); + if (result.isSuccess()) { + listener.onNewFrame(RenderSessionImpl.this.getSession()); + } + } finally { + release(); + } + + listener.done(result); + } + }.start(); + + // always return success since the real status will come through the listener. + return SUCCESS.createResult(); + } + + Result result = removeView(parent, childView); + if (result.isSuccess() == false) { + return result; + } + + return render(false /*freshRender*/); + } + + /** + * Removes a given view from its current parent. + * + * @param view the view to remove from its parent + * + * @return a Result with {@link Status#SUCCESS} or + * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support + * adding views. + */ + private Result removeView(ViewGroup parent, View view) { + try { + parent.removeView(view); + return SUCCESS.createResult(); + } catch (UnsupportedOperationException e) { + // looks like this is a view class that doesn't support children manipulation! + return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); + } + } + + + private void findBackground(RenderResources resources) { + if (getParams().isBgColorOverridden() == false) { + mWindowBackground = resources.findItemInTheme("windowBackground"); + if (mWindowBackground != null) { + mWindowBackground = resources.resolveResValue(mWindowBackground); + } + } + } + + private void findStatusBar(RenderResources resources, DisplayMetrics metrics) { + boolean windowFullscreen = getBooleanThemeValue(resources, + "windowFullscreen", false /*defaultValue*/); + + if (windowFullscreen == false && mWindowIsFloating == false) { + // default value + mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; + + // get the real value + ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, + "status_bar_height"); + + if (value != null) { + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mStatusBarSize = (int)typedValue.getDimension(metrics); + } + } + } + } + + private void findTitleBar(RenderResources resources, DisplayMetrics metrics) { + if (mWindowIsFloating) { + return; + } + + boolean windowNoTitle = getBooleanThemeValue(resources, + "windowNoTitle", false /*defaultValue*/); + + if (windowNoTitle == false) { + + // default size of the window title bar + mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; + + // get value from the theme. + ResourceValue value = resources.findItemInTheme("windowTitleSize"); + + // resolve it + value = resources.resolveResValue(value); + + if (value != null) { + // get the numerical value, if available + TypedValue typedValue = ResourceHelper.getValue(value.getValue()); + if (typedValue != null) { + // compute the pixel value based on the display metrics + mTitleBarSize = (int)typedValue.getDimension(metrics); + } + } + } + } + + private boolean getBooleanThemeValue(RenderResources resources, + String name, boolean defaultValue) { + + // get the title bar flag from the current theme. + ResourceValue value = resources.findItemInTheme(name); + + // because it may reference something else, we resolve it. + value = resources.resolveResValue(value); + + // if there's no value, return the default. + if (value == null || value.getValue() == null) { + return defaultValue; + } + + return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue); + } + + /** + * Post process on a view hierachy that was just inflated. + * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the + * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically + * based on the content of the {@link FrameLayout}. + * @param view the root view to process. + * @param projectCallback callback to the project. + */ + private void postInflateProcess(View view, IProjectCallback projectCallback) + throws PostInflateException { + if (view instanceof TabHost) { + setupTabHost((TabHost)view, projectCallback); + } else if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup)view; + final int count = group.getChildCount(); + for (int c = 0 ; c < count ; c++) { + View child = group.getChildAt(c); + postInflateProcess(child, projectCallback); + } + } + } + + /** + * Sets up a {@link TabHost} object. + * @param tabHost the TabHost to setup. + * @param projectCallback The project callback object to access the project R class. + * @throws PostInflateException + */ + private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) + throws PostInflateException { + // look for the TabWidget, and the FrameLayout. They have their own specific names + View v = tabHost.findViewById(android.R.id.tabs); + + if (v == null) { + throw new PostInflateException( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); + } + + if ((v instanceof TabWidget) == false) { + throw new PostInflateException(String.format( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); + } + + v = tabHost.findViewById(android.R.id.tabcontent); + + if (v == null) { + // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + throw new PostInflateException( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); + } + + if ((v instanceof FrameLayout) == false) { + throw new PostInflateException(String.format( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); + } + + FrameLayout content = (FrameLayout)v; + + // now process the content of the framelayout and dynamically create tabs for it. + final int count = content.getChildCount(); + + // this must be called before addTab() so that the TabHost searches its TabWidget + // and FrameLayout. + tabHost.setup(); + + if (count == 0) { + // Create a dummy child to get a single tab + TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label", + tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details)) + .setContent(new TabHost.TabContentFactory() { + public View createTabContent(String tag) { + return new LinearLayout(getContext()); + } + }); + tabHost.addTab(spec); + return; + } else { + // for each child of the framelayout, add a new TabSpec + for (int i = 0 ; i < count ; i++) { + View child = content.getChildAt(i); + String tabSpec = String.format("tab_spec%d", i+1); + int id = child.getId(); + Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); + String name; + if (resource != null) { + name = resource.getSecond(); + } else { + name = String.format("Tab %d", i+1); // default name if id is unresolved. + } + tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); + } + } + } + + private List<ViewInfo> startVisitingViews(View view, int offset) { + if (view == null) { + return null; + } + + // adjust the offset to this view. + offset += view.getTop(); + + if (view == mContentRoot) { + return visitAllChildren(mContentRoot, offset); + } + + // otherwise, look for mContentRoot in the children + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + + for (int i = 0; i < group.getChildCount(); i++) { + List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset); + if (list != null) { + return list; + } + } + } + + return null; + } + + /** + * Visits a View and its children and generate a {@link ViewInfo} containing the + * bounds of all the views. + * @param view the root View + * @param offset an offset for the view bounds. + */ + private ViewInfo visit(View view, int offset) { + if (view == null) { + return null; + } + + ViewInfo result = new ViewInfo(view.getClass().getName(), + getContext().getViewKey(view), + view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, + view, view.getLayoutParams()); + + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + result.setChildren(visitAllChildren(group, 0 /*offset*/)); + } + + return result; + } + + /** + * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo} + * containing the bounds of all the views. + * @param view the root View + * @param offset an offset for the view bounds. + */ + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset) { + if (viewGroup == null) { + return null; + } + + List<ViewInfo> children = new ArrayList<ViewInfo>(); + for (int i = 0; i < viewGroup.getChildCount(); i++) { + children.add(visit(viewGroup.getChildAt(i), offset)); + } + return children; + } + + + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + } + + public BufferedImage getImage() { + return mImage; + } + + public boolean isAlphaChannelImage() { + return mIsAlphaChannelImage; + } + + public List<ViewInfo> getViewInfos() { + return mViewInfoList; + } + + public Map<String, String> getDefaultProperties(Object viewObject) { + return getContext().getDefaultPropMap(viewObject); + } + + public void setScene(RenderSession session) { + mScene = session; + } + + public RenderSession getSession() { + return mScene; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index f624753..69f46e6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -14,33 +14,45 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import com.android.layoutlib.api.IDensityBasedResourceValue; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IDensityBasedResourceValue.Density; +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.DensityBasedResourceValue; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.ninepatch.NinePatch; +import com.android.ninepatch.NinePatchChunk; +import com.android.resources.Density; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.content.res.ColorStateList; import android.graphics.Bitmap; +import android.graphics.Bitmap_Delegate; +import android.graphics.NinePatch_Delegate; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.util.TypedValue; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Helper class to provide various convertion method used in handling android resources. + * Helper class to provide various conversion method used in handling android resources. */ public final class ResourceHelper { @@ -55,17 +67,21 @@ public final class ResourceHelper { * @return the color as an int * @throw NumberFormatException if the conversion failed. */ - static int getColor(String value) { + public static int getColor(String value) { if (value != null) { if (value.startsWith("#") == false) { - throw new NumberFormatException(); + throw new NumberFormatException( + String.format("Color value '%s' must start with #", value)); } value = value.substring(1); // make sure it's not longer than 32bit if (value.length() > 8) { - throw new NumberFormatException(); + throw new NumberFormatException(String.format( + "Color value '%s' is too long. Format is either" + + "#AARRGGBB, #RRGGBB, #RGB, or #ARGB", + value)); } if (value.length() == 3) { // RGB format @@ -97,48 +113,93 @@ public final class ResourceHelper { throw new NumberFormatException(); } + public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { + String value = resValue.getValue(); + if (value != null) { + // first check if the value is a file (xml most likely) + File f = new File(value); + if (f.isFile()) { + try { + // let the framework inflate the ColorStateList from the XML file, by + // providing an XmlPullParser + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileReader(f)); + + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, context, resValue.isFramework()); + try { + return ColorStateList.createFromXml(context.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + // we'll return null below. + } catch (Exception e) { + // this is an error and not warning since the file existence is + // checked before attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; + } + } else { + // try to load the color state list from an int + try { + int color = ResourceHelper.getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Failed to convert " + value + " into a ColorStateList", e, + null /*data*/); + return null; + } + } + } + + return null; + } + /** * Returns a drawable from the given value. * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, * or an hexadecimal color - * @param context - * @param isFramework indicates whether the resource is a framework resources. - * Framework resources are cached, and loaded only once. + * @param context the current context */ - public static Drawable getDrawable(IResourceValue value, BridgeContext context, boolean isFramework) { - Drawable d = null; - + public static Drawable getDrawable(ResourceValue value, BridgeContext context) { String stringValue = value.getValue(); + if (RenderResources.REFERENCE_NULL.equals(stringValue)) { + return null; + } String lowerCaseValue = stringValue.toLowerCase(); + Density density = Density.MEDIUM; + if (value instanceof DensityBasedResourceValue) { + density = + ((DensityBasedResourceValue)value).getResourceDensity(); + } + + if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { File file = new File(stringValue); if (file.isFile()) { - NinePatch ninePatch = Bridge.getCached9Patch(stringValue, - isFramework ? null : context.getProjectKey()); - - if (ninePatch == null) { - try { - ninePatch = NinePatch.load(file.toURL(), false /* convert */); - - Bridge.setCached9Patch(stringValue, ninePatch, - isFramework ? null : context.getProjectKey()); - } catch (MalformedURLException e) { - // URL is wrong, we'll return null below - } catch (IOException e) { - // failed to read the file, we'll return null below. - } - } - - if (ninePatch != null) { - return new NinePatchDrawable(ninePatch); + try { + return getNinePatchDrawable( + new FileInputStream(file), density, value.isFramework(), + stringValue, context); + } catch (IOException e) { + // failed to read the file, we'll return null below. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed lot load " + file.getAbsolutePath(), e, null /*data*/); } } return null; } else if (lowerCaseValue.endsWith(".xml")) { - // create a blockparser for the file + // create a block parser for the file File f = new File(stringValue); if (f.isFile()) { try { @@ -147,16 +208,23 @@ public final class ResourceHelper { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - d = Drawable.createFromXml(context.getResources(), - new BridgeXmlBlockParser(parser, context, isFramework)); - return d; - } catch (XmlPullParserException e) { - context.getLogger().error(e); - } catch (FileNotFoundException e) { - // will not happen, since we pre-check - } catch (IOException e) { - context.getLogger().error(e); + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, context, value.isFramework()); + try { + return Drawable.createFromXml(context.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (Exception e) { + // this is an error and not warning since the file existence is checked before + // attempting to parse it. + Bridge.getLog().error(null, "Failed to parse file " + stringValue, + e, null /*data*/); } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("File %s does not exist (or is not a file)", stringValue), + null /*data*/); } return null; @@ -165,42 +233,20 @@ public final class ResourceHelper { if (bmpFile.isFile()) { try { Bitmap bitmap = Bridge.getCachedBitmap(stringValue, - isFramework ? null : context.getProjectKey()); + value.isFramework() ? null : context.getProjectKey()); if (bitmap == null) { - bitmap = new Bitmap(bmpFile); - try { - bitmap.setDensity(Density.MEDIUM.getValue()); - } catch (NoClassDefFoundError error) { - // look like we're running in an older version of ADT that doesn't - // include the new layoutlib_api. Let's just ignore this, the drawing - // will just be wrong. - } + bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, + density); Bridge.setCachedBitmap(stringValue, bitmap, - isFramework ? null : context.getProjectKey()); - } - - try { - if (value instanceof IDensityBasedResourceValue) { - Density density = ((IDensityBasedResourceValue)value).getDensity(); - if (density != Density.MEDIUM) { - // create a copy of the bitmap - bitmap = Bitmap.createBitmap(bitmap); - - // apply the density - bitmap.setDensity(density.getValue()); - } - } - } catch (NoClassDefFoundError error) { - // look like we're running in an older version of ADT that doesn't include - // the new layoutlib_api. Let's just ignore this, the drawing will just be - // wrong. + value.isFramework() ? null : context.getProjectKey()); } return new BitmapDrawable(context.getResources(), bitmap); } catch (IOException e) { // we'll return null below - // TODO: log the error. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/); } } else { // attempt to get a color from the value @@ -209,7 +255,9 @@ public final class ResourceHelper { return new ColorDrawable(color); } catch (NumberFormatException e) { // we'll return null below. - // TODO: log the error + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, + "Failed to convert " + stringValue + " into a drawable", e, + null /*data*/); } } } @@ -217,6 +265,52 @@ public final class ResourceHelper { return null; } + private static Drawable getNinePatchDrawable(InputStream inputStream, Density density, + boolean isFramework, String cacheKey, BridgeContext context) throws IOException { + // see if we still have both the chunk and the bitmap in the caches + NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey, + isFramework ? null : context.getProjectKey()); + Bitmap bitmap = Bridge.getCachedBitmap(cacheKey, + isFramework ? null : context.getProjectKey()); + + // if either chunk or bitmap is null, then we reload the 9-patch file. + if (chunk == null || bitmap == null) { + try { + NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/, + false /* convert */); + if (ninePatch != null) { + if (chunk == null) { + chunk = ninePatch.getChunk(); + + Bridge.setCached9Patch(cacheKey, chunk, + isFramework ? null : context.getProjectKey()); + } + + if (bitmap == null) { + bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(), + false /*isMutable*/, + density); + + Bridge.setCachedBitmap(cacheKey, bitmap, + isFramework ? null : context.getProjectKey()); + } + } + } catch (MalformedURLException e) { + // URL is wrong, we'll return null below + } + } + + if (chunk != null && bitmap != null) { + int[] padding = chunk.getPadding(); + Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]); + + return new NinePatchDrawable(context.getResources(), bitmap, + NinePatch_Delegate.serialize(chunk), + paddingRect, null); + } + + return null; + } // ------- TypedValue stuff // This is taken from //device/libs/utils/ResourceTypes.cpp @@ -267,7 +361,7 @@ public final class ResourceHelper { */ public static boolean stringToFloat(String s, TypedValue outValue) { // remove the space before and after - s.trim(); + s = s.trim(); int len = s.length(); if (len <= 0) { @@ -379,3 +473,4 @@ public final class ResourceHelper { return false; } } + diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java new file mode 100644 index 0000000..9bd0015 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl; + +import java.util.ArrayList; + +/** + * Custom Stack implementation on top of an {@link ArrayList} instead of + * using {@link java.util.Stack} which is on top of a vector. + * + * @param <T> + */ +public class Stack<T> extends ArrayList<T> { + + private static final long serialVersionUID = 1L; + + public Stack() { + super(); + } + + public Stack(int size) { + super(size); + } + + /** + * Pushes the given object to the stack + * @param object the object to push + */ + public void push(T object) { + add(object); + } + + /** + * Remove the object at the top of the stack and returns it. + * @return the removed object or null if the stack was empty. + */ + public T pop() { + if (size() > 0) { + return remove(size() - 1); + } + + return null; + } + + /** + * Returns the object at the top of the stack. + * @return the object at the top or null if the stack is empty. + */ + public T peek() { + if (size() > 0) { + return get(size() - 1); + } + + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java new file mode 100644 index 0000000..82eab85 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.util; + +public class Debug { + + public final static boolean DEBUG = false; + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java new file mode 100644 index 0000000..4d0c9ce --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.util; + + +import com.android.internal.util.ArrayUtils; + +import android.util.SparseArray; + +import java.lang.ref.WeakReference; + +/** + * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added + * to it. When the array is compacted, not only deleted indices but also empty references + * are removed, making the array efficient at removing references that were reclaimed. + * + * The code is taken from {@link SparseArray} directly and adapted to use weak references. + * + * Because our usage means that we never actually call {@link #remove(int)} or {@link #delete(int)}, + * we must manually check if there are reclaimed references to trigger an internal compact step + * (which is normally only triggered when an item is manually removed). + * + * SparseArrays map integers to Objects. Unlike a normal array of Objects, + * there can be gaps in the indices. It is intended to be more efficient + * than using a HashMap to map Integers to Objects. + */ +@SuppressWarnings("unchecked") +public class SparseWeakArray<E> { + + private static final Object DELETED_REF = new Object(); + private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF); + private boolean mGarbage = false; + + /** + * Creates a new SparseArray containing no mappings. + */ + public SparseWeakArray() { + this(10); + } + + /** + * Creates a new SparseArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public SparseWeakArray(int initialCapacity) { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + + mKeys = new int[initialCapacity]; + mValues = new WeakReference[initialCapacity]; + mSize = 0; + } + + /** + * Gets the Object mapped from the specified key, or <code>null</code> + * if no such mapping has been made. + */ + public E get(int key) { + return get(key, null); + } + + /** + * Gets the Object mapped from the specified key, or the specified Object + * if no such mapping has been made. + */ + public E get(int key, E valueIfKeyNotFound) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i < 0 || mValues[i] == DELETED || mValues[i].get() == null) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i].get(); + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(int key) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + } + } + } + + /** + * Alias for {@link #delete(int)}. + */ + public void remove(int key) { + delete(key); + } + + /** + * Removes the mapping at the specified index. + */ + public void removeAt(int index) { + if (mValues[index] != DELETED) { + mValues[index] = DELETED; + mGarbage = true; + } + } + + private void gc() { + int n = mSize; + int o = 0; + int[] keys = mKeys; + WeakReference<?>[] values = mValues; + + for (int i = 0; i < n; i++) { + WeakReference<?> val = values[i]; + + // Don't keep any non DELETED values, but only the one that still have a valid + // reference. + if (val != DELETED && val.get() != null) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + + int newSize = ArrayUtils.idealIntArraySize(mSize); + if (newSize < mKeys.length) { + int[] nkeys = new int[newSize]; + WeakReference<?>[] nvalues = new WeakReference[newSize]; + + System.arraycopy(mKeys, 0, nkeys, 0, newSize); + System.arraycopy(mValues, 0, nvalues, 0, newSize); + + mKeys = nkeys; + mValues = nvalues; + } + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(int key, E value) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + mValues[i] = new WeakReference(value); + } else { + i = ~i; + + if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) { + mKeys[i] = key; + mValues[i] = new WeakReference(value); + return; + } + + if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) { + gc(); + + // Search again because indices may have changed. + i = ~binarySearch(mKeys, 0, mSize, key); + } + + if (mSize >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(mSize + 1); + + int[] nkeys = new int[n]; + WeakReference<?>[] nvalues = new WeakReference[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + if (mSize - i != 0) { + // Log.e("SparseArray", "move " + (mSize - i)); + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + + mKeys[i] = key; + mValues[i] = new WeakReference(value); + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public int keyAt(int index) { + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public E valueAt(int index) { + if (mGarbage) { + gc(); + } + + return (E) mValues[index].get(); + } + + /** + * Given an index in the range <code>0...size()-1</code>, sets a new + * value for the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public void setValueAt(int index, E value) { + if (mGarbage) { + gc(); + } + + mValues[index] = new WeakReference(value); + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(int key) { + if (mGarbage) { + gc(); + } + + return binarySearch(mKeys, 0, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) + if (mValues[i].get() == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseArray. + */ + public void clear() { + int n = mSize; + WeakReference<?>[] values = mValues; + + for (int i = 0; i < n; i++) { + values[i] = null; + } + + mSize = 0; + mGarbage = false; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(int key, E value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) { + gc(); + } + + int pos = mSize; + if (pos >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(pos + 1); + + int[] nkeys = new int[n]; + WeakReference<?>[] nvalues = new WeakReference[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + mKeys[pos] = key; + mValues[pos] = new WeakReference(value); + mSize = pos + 1; + } + + private boolean hasReclaimedRefs() { + for (int i = 0 ; i < mSize ; i++) { + if (mValues[i].get() == null) { // DELETED.get() never returns null. + return true; + } + } + + return false; + } + + private static int binarySearch(int[] a, int start, int len, int key) { + int high = start + len, low = start - 1, guess; + + while (high - low > 1) { + guess = (high + low) / 2; + + if (a[guess] < key) + low = guess; + else + high = guess; + } + + if (high == start + len) + return ~(start + len); + else if (a[high] == key) + return high; + else + return ~high; + } + + private int[] mKeys; + private WeakReference<?>[] mValues; + private int mSize; +} diff --git a/tools/layoutlib/bridge/tests/.classpath b/tools/layoutlib/bridge/tests/.classpath new file mode 100644 index 0000000..9cc2433 --- /dev/null +++ b/tools/layoutlib/bridge/tests/.classpath @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_bridge"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/> + <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="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/tools/layoutlib/bridge/tests/.project b/tools/layoutlib/bridge/tests/.project new file mode 100644 index 0000000..2325eed --- /dev/null +++ b/tools/layoutlib/bridge/tests/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>layoutlib_bridge-tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk new file mode 100644 index 0000000..e303638 --- /dev/null +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2011 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE := layoutlib-tests +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := layoutlib kxml2-2.3.0 junit + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java deleted file mode 100644 index 23351ab..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2008 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 com.android.layoutlib.bridge; - -import com.android.ninepatch.NinePatch; - -import java.net.URL; - -import junit.framework.TestCase; - -public class NinePatchTest extends TestCase { - - private NinePatch mPatch; - - @Override - protected void setUp() throws Exception { - URL url = this.getClass().getClassLoader().getResource( - "com/android/layoutlib/testdata/button.9.png"); - - mPatch = NinePatch.load(url, false /* convert */); - } - - public void test9PatchLoad() throws Exception { - assertNotNull(mPatch); - } - - public void test9PatchMinSize() { - int[] padding = new int[4]; - mPatch.getPadding(padding); - assertEquals(13, padding[0]); - assertEquals(3, padding[1]); - assertEquals(13, padding[2]); - assertEquals(4, padding[3]); - assertEquals(36, mPatch.getWidth()); - assertEquals(25, mPatch.getHeight()); - } - -} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java deleted file mode 100644 index e0dc55f..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2009 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 com.android.layoutlib.bridge; - -import java.lang.reflect.Method; - -import junit.framework.TestCase; - -public class TestClassReplacement extends TestCase { - - public void testClassReplacements() { - // TODO: we want to test all the classes. For now only Paint passes the tests. -// final String[] classes = CreateInfo.RENAMED_CLASSES; - final String[] classes = new String[] { - "android.graphics.Paint", "android.graphics._Original_Paint" - }; - final int count = classes.length; - for (int i = 0 ; i < count ; i += 2) { - loadAndCompareClasses(classes[i], classes[i+1]); - } - } - - private void loadAndCompareClasses(String newClassName, String oldClassName) { - // load the classes - try { - Class<?> newClass = TestClassReplacement.class.getClassLoader().loadClass(newClassName); - Class<?> oldClass = TestClassReplacement.class.getClassLoader().loadClass(oldClassName); - - compare(newClass, oldClass); - } catch (ClassNotFoundException e) { - fail("Failed to load class: " + e.getMessage()); - } - } - - private void compare(Class<?> newClass, Class<?> oldClass) { - // first compare the methods. - Method[] newClassMethods = newClass.getDeclaredMethods(); - Method[] oldClassMethods = oldClass.getDeclaredMethods(); - - for (Method oldMethod : oldClassMethods) { - // we ignore anything that starts with native - if (oldMethod.getName().startsWith("native")) { - continue; - } - boolean found = false; - for (Method newMethod : newClassMethods) { - if (compareMethods(newClass, newMethod, oldClass, oldMethod)) { - found = true; - break; - } - } - - if (found == false) { - fail(String.format("Unable to find %1$s", oldMethod.toGenericString())); - } - } - - // TODO: check (somehow?) that the methods that were removed from the original class - // have been put back in the new class! - // For this we need the original unmodified class (ie renamed, but w/o the methods removed) - } - - private boolean compareMethods(Class<?> newClass, Method newMethod, - Class<?> oldClass, Method oldMethod) { - // first check the name of the method - if (newMethod.getName().equals(oldMethod.getName()) == false) { - return false; - } - - // check the return value - Class<?> oldReturnType = oldMethod.getReturnType(); - // if it's the old class, or if it's a inner class of the oldclass, we need to change this. - oldReturnType = adapt(oldReturnType, newClass, oldClass); - - // compare the return types - Class<?> newReturnType = newMethod.getReturnType(); - if (newReturnType.equals(oldReturnType) == false) { - return false; - } - - // now check the parameters type. - Class<?>[] oldParameters = oldMethod.getParameterTypes(); - Class<?>[] newParemeters = newMethod.getParameterTypes(); - if (oldParameters.length != newParemeters.length) { - return false; - } - - for (int i = 0 ; i < oldParameters.length ; i++) { - if (newParemeters[i].equals(adapt(oldParameters[i], newClass, oldClass)) == false) { - return false; - } - } - - return true; - } - - /** - * Adapts a class to deal with renamed classes. - * <p/>For instance if old class is <code>android.graphics._Original_Paint</code> and the - * new class is <code>android.graphics.Paint</code> and the class to adapt is - * <code>android.graphics._Original_Paint$Cap</code>, then the method will return a - * {@link Class} object representing <code>android.graphics.Paint$Cap</code>. - * <p/> - * This method will also ensure that all renamed classes contains all the proper inner classes - * that they should be declaring. - * @param theClass the class to adapt - * @param newClass the new class object - * @param oldClass the old class object - * @return the adapted class. - * @throws ClassNotFoundException - */ - private Class<?> adapt(Class<?> theClass, Class<?> newClass, Class<?> oldClass) { - // only look for a new class if it's not primitive as Class.forName() would fail otherwise. - if (theClass.isPrimitive() == false) { - String n = theClass.getName().replace(oldClass.getName(), newClass.getName()); - try { - return Class.forName(n); - } catch (ClassNotFoundException e) { - fail("Missing class: " + n); - } - } - - return theClass; - } -} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png Binary files differdeleted file mode 100644 index 9d52f40..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png +++ /dev/null diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java index 6e14e82..ec4edac 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java +++ b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java @@ -14,19 +14,14 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; - -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics._Original_Paint; -import android.text.TextPaint; +package android.graphics; import junit.framework.TestCase; /** * */ -public class AndroidGraphicsTests extends TestCase { +public class Matrix_DelegateTest extends TestCase { @Override protected void setUp() throws Exception { @@ -38,14 +33,17 @@ public class AndroidGraphicsTests extends TestCase { super.tearDown(); } - public void testMatrix() { + public void testIdentity() { Matrix m1 = new Matrix(); assertTrue(m1.isIdentity()); m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 }); assertTrue(m1.isIdentity()); + } + public void testCopyConstructor() { + Matrix m1 = new Matrix(); Matrix m2 = new Matrix(m1); float[] v1 = new float[9]; @@ -57,17 +55,4 @@ public class AndroidGraphicsTests extends TestCase { assertEquals(v1[i], v2[i]); } } - - public void testPaint() { - _Original_Paint o = new _Original_Paint(); - assertNotNull(o); - - Paint p = new Paint(); - assertNotNull(p); - } - - public void textTextPaint() { - TextPaint p = new TextPaint(); - assertNotNull(p); - } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java new file mode 100644 index 0000000..d3218db --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2010 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 com.android.layoutlib.bridge; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import com.android.tools.layoutlib.create.CreateInfo; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +/** + * Tests that native delegate classes implement all the required methods. + * + * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that + * have their native methods reimplemented through a delegate. + * + * Since the reimplemented methods are not native anymore, we look for the annotation + * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same + * as the modified class with _Delegate added as a suffix). + * If the original native method is not static, then we make sure the delegate method also + * include the original class as first parameter (to access "this"). + * + */ +public class TestDelegates extends TestCase { + + public void testNativeDelegates() { + + final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES; + final int count = classes.length; + for (int i = 0 ; i < count ; i++) { + loadAndCompareClasses(classes[i], classes[i] + "_Delegate"); + } + } + + public void testMethodDelegates() { + final String[] methods = CreateInfo.DELEGATE_METHODS; + final int count = methods.length; + for (int i = 0 ; i < count ; i++) { + String methodName = methods[i]; + + // extract the class name + String className = methodName.substring(0, methodName.indexOf('#')); + String targetClassName = className.replace('$', '_') + "_Delegate"; + + loadAndCompareClasses(className, targetClassName); + } + } + + private void loadAndCompareClasses(String originalClassName, String delegateClassName) { + // load the classes + try { + ClassLoader classLoader = TestDelegates.class.getClassLoader(); + Class<?> originalClass = classLoader.loadClass(originalClassName); + Class<?> delegateClass = classLoader.loadClass(delegateClassName); + + compare(originalClass, delegateClass); + } catch (ClassNotFoundException e) { + fail("Failed to load class: " + e.getMessage()); + } catch (SecurityException e) { + fail("Failed to load class: " + e.getMessage()); + } + } + + private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException { + List<Method> checkedDelegateMethods = new ArrayList<Method>(); + + // loop on the methods of the original class, and for the ones that are annotated + // with @LayoutlibDelegate, look for a matching method in the delegate class. + // The annotation is automatically added by layoutlib_create when it replace a method + // by a call to a delegate + Method[] originalMethods = originalClass.getDeclaredMethods(); + for (Method originalMethod : originalMethods) { + // look for methods that are delegated: they have the LayoutlibDelegate annotation + if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) { + continue; + } + + // get the signature. + Class<?>[] parameters = originalMethod.getParameterTypes(); + + // if the method is not static, then the class is added as the first parameter + // (for "this") + if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) { + + Class<?>[] newParameters = new Class<?>[parameters.length + 1]; + newParameters[0] = originalClass; + System.arraycopy(parameters, 0, newParameters, 1, parameters.length); + parameters = newParameters; + } + + // if the original class is an inner class that's not static, then + // we add this on the enclosing class at the beginning + if (originalClass.getEnclosingClass() != null && + (originalClass.getModifiers() & Modifier.STATIC) == 0) { + Class<?>[] newParameters = new Class<?>[parameters.length + 1]; + newParameters[0] = originalClass.getEnclosingClass(); + System.arraycopy(parameters, 0, newParameters, 1, parameters.length); + parameters = newParameters; + } + + try { + // try to load the method with the given parameter types. + Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(), + parameters); + + // check that the method has the annotation + assertNotNull( + String.format( + "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation", + delegateMethod.getName(), + originalClass.getName()), + delegateMethod.getAnnotation(LayoutlibDelegate.class)); + + // check that the method is static + assertTrue( + String.format( + "Delegate method %1$s for class %2$s is not static", + delegateMethod.getName(), + originalClass.getName()), + (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC); + + // add the method as checked. + checkedDelegateMethods.add(delegateMethod); + } catch (NoSuchMethodException e) { + String name = getMethodName(originalMethod, parameters); + fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); + } + } + + // look for dead (delegate) code. + // This looks for all methods in the delegate class, and if they have the + // @LayoutlibDelegate annotation, make sure they have been previously found as a + // match for a method in the original class. + // If not, this means the method is a delegate for a method that either doesn't exist + // anymore or is not delegated anymore. + Method[] delegateMethods = delegateClass.getDeclaredMethods(); + for (Method delegateMethod : delegateMethods) { + // look for methods that are delegates: they have the LayoutlibDelegate annotation + if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) { + continue; + } + + assertTrue( + String.format( + "Delegate method %1$s.%2$s is not used anymore and must be removed", + delegateClass.getName(), + getMethodName(delegateMethod)), + checkedDelegateMethods.contains(delegateMethod)); + } + + } + + private String getMethodName(Method method) { + return getMethodName(method, method.getParameterTypes()); + } + + private String getMethodName(Method method, Class<?>[] parameters) { + // compute a full class name that's long but not too long. + StringBuilder sb = new StringBuilder(method.getName() + "("); + for (int j = 0; j < parameters.length; j++) { + Class<?> theClass = parameters[j]; + sb.append(theClass.getName()); + int dimensions = 0; + while (theClass.isArray()) { + dimensions++; + theClass = theClass.getComponentType(); + } + for (int i = 0; i < dimensions; i++) { + sb.append("[]"); + } + if (j < (parameters.length - 1)) { + sb.append(","); + } + } + sb.append(")"); + + return sb.toString(); + } +} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index db1262f..3252fb4 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.layoutlib.bridge; +package com.android.layoutlib.bridge.android; + +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import org.kxml2.io.KXmlParser; import org.w3c.dom.Node; diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/layout1.xml b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/testdata/layout1.xml index b8fc947..b8fc947 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/layout1.xml +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/testdata/layout1.xml diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index c59e20d..65a64cd 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -195,5 +195,22 @@ example, the inner class Paint$Style in the Paint class should be discarded and bridge will provide its own implementation. +- References - +-------------- + + +The JVM Specification 2nd edition: + http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html + +Understanding bytecode: + http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/ + +Bytecode opcode list: + http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings + +ASM user guide: + http://download.forge.objectweb.org/asm/asm-guide.pdf + + -- end diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java new file mode 100644 index 0000000..9a48ea6 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes a method that has been converted to a delegate by layoutlib_create. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface LayoutlibDelegate { +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 7b55ed3e..a9ede26 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -28,9 +28,9 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import java.util.Map.Entry; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; @@ -60,38 +60,56 @@ public class AsmGenerator { * old-FQCN to rename and they get erased as they get renamed. At the end, classes still * left here are not in the code base anymore and thus were not renamed. */ private HashSet<String> mClassesNotRenamed; - /** A map { FQCN => map { list of return types to delete from the FQCN } }. */ + /** A map { FQCN => set { list of return types to delete from the FQCN } }. */ private HashMap<String, Set<String>> mDeleteReturns; + /** A map { FQCN => set { method names } } of methods to rewrite as delegates. + * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */ + private final HashMap<String, Set<String>> mDelegateMethods; /** * Creates a new generator that can generate the output JAR with the stubbed classes. - * + * * @param log Output logger. * @param osDestJar The path of the destination JAR to create. - * @param injectClasses The list of class from layoutlib_create to inject in layoutlib. - * @param stubMethods The list of methods to stub out. Each entry must be in the form - * "package.package.OuterClass$InnerClass#MethodName". - * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN - * of class to replace followed by the new FQCN. - * @param deleteReturns List of classes for which the methods returning them should be deleted. - * The array contains a list of null terminated section starting with the name of the class - * to rename in which the methods are deleted, followed by a list of return types identifying - * the methods to delete. + * @param createInfo Creation parameters. Must not be null. */ - public AsmGenerator(Log log, String osDestJar, - Class<?>[] injectClasses, - String[] stubMethods, - String[] renameClasses, String[] deleteReturns) { + public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) { mLog = log; mOsDestJar = osDestJar; - mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0]; - mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) : - new HashSet<String>(); + mInjectClasses = createInfo.getInjectedClasses(); + mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods())); + + // Create the map/set of methods to change to delegates + mDelegateMethods = new HashMap<String, Set<String>>(); + for (String signature : createInfo.getDelegateMethods()) { + int pos = signature.indexOf('#'); + if (pos <= 0 || pos >= signature.length() - 1) { + continue; + } + String className = binaryToInternalClassName(signature.substring(0, pos)); + String methodName = signature.substring(pos + 1); + Set<String> methods = mDelegateMethods.get(className); + if (methods == null) { + methods = new HashSet<String>(); + mDelegateMethods.put(className, methods); + } + methods.add(methodName); + } + for (String className : createInfo.getDelegateClassNatives()) { + className = binaryToInternalClassName(className); + Set<String> methods = mDelegateMethods.get(className); + if (methods == null) { + methods = new HashSet<String>(); + mDelegateMethods.put(className, methods); + } + methods.add(DelegateClassAdapter.ALL_NATIVES); + } // Create the map of classes to rename. mRenameClasses = new HashMap<String, String>(); mClassesNotRenamed = new HashSet<String>(); - int n = renameClasses == null ? 0 : renameClasses.length; + String[] renameClasses = createInfo.getRenamedClasses(); + int n = renameClasses.length; for (int i = 0; i < n; i += 2) { assert i + 1 < n; // The ASM class names uses "/" separators, whereas regular FQCN use "." @@ -100,38 +118,37 @@ public class AsmGenerator { mRenameClasses.put(oldFqcn, newFqcn); mClassesNotRenamed.add(oldFqcn); } - + // create the map of renamed class -> return type of method to delete. mDeleteReturns = new HashMap<String, Set<String>>(); - if (deleteReturns != null) { - Set<String> returnTypes = null; - String renamedClass = null; - for (String className : deleteReturns) { - // if we reach the end of a section, add it to the main map - if (className == null) { - if (returnTypes != null) { - mDeleteReturns.put(renamedClass, returnTypes); - } - - renamedClass = null; - continue; - } - - // if the renamed class is null, this is the beginning of a section - if (renamedClass == null) { - renamedClass = binaryToInternalClassName(className); - continue; - } - - // just a standard return type, we add it to the list. - if (returnTypes == null) { - returnTypes = new HashSet<String>(); + String[] deleteReturns = createInfo.getDeleteReturns(); + Set<String> returnTypes = null; + String renamedClass = null; + for (String className : deleteReturns) { + // if we reach the end of a section, add it to the main map + if (className == null) { + if (returnTypes != null) { + mDeleteReturns.put(renamedClass, returnTypes); } - returnTypes.add(binaryToInternalClassName(className)); + + renamedClass = null; + continue; } + + // if the renamed class is null, this is the beginning of a section + if (renamedClass == null) { + renamedClass = binaryToInternalClassName(className); + continue; + } + + // just a standard return type, we add it to the list. + if (returnTypes == null) { + returnTypes = new HashSet<String>(); + } + returnTypes.add(binaryToInternalClassName(className)); } } - + /** * Returns the list of classes that have not been renamed yet. * <p/> @@ -163,12 +180,12 @@ public class AsmGenerator { public void setDeps(Map<String, ClassReader> deps) { mDeps = deps; } - + /** Gets the map of classes to output as-is, except if they have native methods */ public Map<String, ClassReader> getKeep() { return mKeep; } - + /** Gets the map of dependencies that must be completely stubbed */ public Map<String, ClassReader> getDeps() { return mDeps; @@ -177,7 +194,7 @@ public class AsmGenerator { /** Generates the final JAR */ public void generate() throws FileNotFoundException, IOException { TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); - + for (Class<?> clazz : mInjectClasses) { String name = classToEntryPath(clazz); InputStream is = ClassLoader.getSystemResourceAsStream(name); @@ -186,7 +203,7 @@ public class AsmGenerator { name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } - + for (Entry<String, ClassReader> entry : mDeps.entrySet()) { ClassReader cr = entry.getValue(); byte[] b = transform(cr, true /* stubNativesOnly */); @@ -211,8 +228,8 @@ public class AsmGenerator { /** * Writes the JAR file. - * - * @param outStream The file output stream were to write the JAR. + * + * @param outStream The file output stream were to write the JAR. * @param all The map of all classes to output. * @throws IOException if an I/O error has occurred */ @@ -236,7 +253,7 @@ public class AsmGenerator { String classNameToEntryPath(String className) { return className.replaceAll("\\.", "/").concat(".class"); } - + /** * Utility method to get the JAR entry path from a Class name. * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class" @@ -248,30 +265,32 @@ public class AsmGenerator { name = "$" + clazz.getSimpleName() + name; clazz = parent; } - return classNameToEntryPath(clazz.getCanonicalName() + name); + return classNameToEntryPath(clazz.getCanonicalName() + name); } /** * Transforms a class. * <p/> * There are 3 kind of transformations: - * + * * 1- For "mock" dependencies classes, we want to remove all code from methods and replace * by a stub. Native methods must be implemented with this stub too. Abstract methods are * left intact. Modified classes must be overridable (non-private, non-final). * Native methods must be made non-final, non-private. - * + * * 2- For "keep" classes, we want to rewrite all native methods as indicated above. * If a class has native methods, it must also be made non-private, non-final. - * + * * Note that unfortunately static methods cannot be changed to non-static (since static and * non-static are invoked differently.) */ byte[] transform(ClassReader cr, boolean stubNativesOnly) { boolean hasNativeMethods = hasNativeMethods(cr); + + // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass) String className = cr.getClassName(); - + String newName = transformName(className); // transformName returns its input argument if there's no need to rename the class if (newName != className) { @@ -288,16 +307,28 @@ public class AsmGenerator { // Rewrite the new class from scratch, without reusing the constant pool from the // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - + ClassVisitor rv = cw; if (newName != className) { rv = new RenameClassAdapter(cw, className, newName); } - - TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods, + + ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), newName, rv, stubNativesOnly, stubNativesOnly || hasNativeMethods); + + Set<String> delegateMethods = mDelegateMethods.get(className); + if (delegateMethods != null && !delegateMethods.isEmpty()) { + // If delegateMethods only contains one entry ALL_NATIVES and the class is + // known to have no native methods, just skip this step. + if (hasNativeMethods || + !(delegateMethods.size() == 1 && + delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) { + cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods); + } + } + cr.accept(cv, 0 /* flags */); return cw.toByteArray(); } @@ -323,7 +354,7 @@ public class AsmGenerator { return newName + className.substring(pos); } } - + return className; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 2ed8641..4b62e43 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -16,51 +16,157 @@ package com.android.tools.layoutlib.create; -public class CreateInfo { +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Describes the work to be done by {@link AsmGenerator}. + */ +public final class CreateInfo implements ICreateInfo { + + /** + * Returns the list of class from layoutlib_create to inject in layoutlib. + * The list can be empty but must not be null. + */ + public Class<?>[] getInjectedClasses() { + return INJECTED_CLASSES; + } + + /** + * Returns the list of methods to rewrite as delegates. + * The list can be empty but must not be null. + */ + public String[] getDelegateMethods() { + return DELEGATE_METHODS; + } + + /** + * Returns the list of classes on which to delegate all native methods. + * The list can be empty but must not be null. + */ + public String[] getDelegateClassNatives() { + return DELEGATE_CLASS_NATIVES; + } + + /** + * Returns The list of methods to stub out. Each entry must be in the form + * "package.package.OuterClass$InnerClass#MethodName". + * The list can be empty but must not be null. + */ + public String[] getOverriddenMethods() { + return OVERRIDDEN_METHODS; + } + + /** + * Returns the list of classes to rename, must be an even list: the binary FQCN + * of class to replace followed by the new FQCN. + * The list can be empty but must not be null. + */ + public String[] getRenamedClasses() { + return RENAMED_CLASSES; + } + + /** + * Returns the list of classes for which the methods returning them should be deleted. + * The array contains a list of null terminated section starting with the name of the class + * to rename in which the methods are deleted, followed by a list of return types identifying + * the methods to delete. + * The list can be empty but must not be null. + */ + public String[] getDeleteReturns() { + return DELETE_RETURNS; + } + + //----- + /** * The list of class from layoutlib_create to inject in layoutlib. */ - public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] { + private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] { OverrideMethod.class, MethodListener.class, MethodAdapter.class, - CreateInfo.class + ICreateInfo.class, + CreateInfo.class, + LayoutlibDelegate.class }; /** + * The list of methods to rewrite as delegates. + */ + private final static String[] DELEGATE_METHODS = new String[] { + "android.content.res.Resources$Theme#obtainStyledAttributes", + "android.content.res.Resources$Theme#resolveAttribute", + "android.graphics.BitmapFactory#finishDecode", + "android.os.Handler#sendMessageAtTime", + "android.os.Build#getString", + "android.view.LayoutInflater#parseInclude", + "android.view.View#isInEditMode", + "com.android.internal.util.XmlUtils#convertValueToInt", + // TODO: comment out once DelegateClass is working + }; + + /** + * The list of classes on which to delegate all native methods. + */ + private final static String[] DELEGATE_CLASS_NATIVES = new String[] { + "android.graphics.AvoidXfermode", + "android.graphics.Bitmap", + "android.graphics.BitmapFactory", + "android.graphics.BitmapShader", + "android.graphics.BlurMaskFilter", + "android.graphics.Canvas", + "android.graphics.ColorFilter", + "android.graphics.ColorMatrixColorFilter", + "android.graphics.ComposePathEffect", + "android.graphics.ComposeShader", + "android.graphics.CornerPathEffect", + "android.graphics.DashPathEffect", + "android.graphics.DiscretePathEffect", + "android.graphics.DrawFilter", + "android.graphics.EmbossMaskFilter", + "android.graphics.LayerRasterizer", + "android.graphics.LightingColorFilter", + "android.graphics.LinearGradient", + "android.graphics.MaskFilter", + "android.graphics.Matrix", + "android.graphics.NinePatch", + "android.graphics.Paint", + "android.graphics.PaintFlagsDrawFilter", + "android.graphics.Path", + "android.graphics.PathDashPathEffect", + "android.graphics.PathEffect", + "android.graphics.PixelXorXfermode", + "android.graphics.PorterDuffColorFilter", + "android.graphics.PorterDuffXfermode", + "android.graphics.RadialGradient", + "android.graphics.Rasterizer", + "android.graphics.Region", + "android.graphics.Shader", + "android.graphics.SumPathEffect", + "android.graphics.SweepGradient", + "android.graphics.Typeface", + "android.graphics.Xfermode", + "android.os.SystemClock", + "android.util.FloatMath", + }; + + /** * The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". */ - public final static String[] OVERRIDDEN_METHODS = new String[] { - "android.view.View#isInEditMode", - "android.content.res.Resources$Theme#obtainStyledAttributes", - }; + private final static String[] OVERRIDDEN_METHODS = new String[] { + }; /** * The list of classes to rename, must be an even list: the binary FQCN * of class to replace followed by the new FQCN. */ - public final static String[] RENAMED_CLASSES = + private final static String[] RENAMED_CLASSES = new String[] { - "android.graphics.Bitmap", "android.graphics._Original_Bitmap", - "android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory", - "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader", - "android.graphics.Canvas", "android.graphics._Original_Canvas", - "android.graphics.ComposeShader", "android.graphics._Original_ComposeShader", - "android.graphics.DashPathEffect", "android.graphics._Original_DashPathEffect", - "android.graphics.LinearGradient", "android.graphics._Original_LinearGradient", - "android.graphics.Matrix", "android.graphics._Original_Matrix", - "android.graphics.Paint", "android.graphics._Original_Paint", - "android.graphics.Path", "android.graphics._Original_Path", - "android.graphics.PorterDuffXfermode", "android.graphics._Original_PorterDuffXfermode", - "android.graphics.RadialGradient", "android.graphics._Original_RadialGradient", - "android.graphics.Shader", "android.graphics._Original_Shader", - "android.graphics.SweepGradient", "android.graphics._Original_SweepGradient", - "android.graphics.Typeface", "android.graphics._Original_Typeface", "android.os.ServiceManager", "android.os._Original_ServiceManager", - "android.util.FloatMath", "android.util._Original_FloatMath", "android.view.SurfaceView", "android.view._Original_SurfaceView", "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", + "android.webkit.WebView", "android.webkit._Original_WebView", }; /** @@ -69,15 +175,8 @@ public class CreateInfo { * to rename in which the methods are deleted, followed by a list of return types identifying * the methods to delete. */ - public final static String[] REMOVED_METHODS = + private final static String[] DELETE_RETURNS = new String[] { - "android.graphics.Paint", // class to delete methods from - "android.graphics.Paint$Align", // list of type identifying methods to delete - "android.graphics.Paint$Style", - "android.graphics.Paint$Join", - "android.graphics.Paint$Cap", - "android.graphics.Paint$FontMetrics", - "android.graphics.Paint$FontMetricsInt", null }; // separator, for next class/methods list. } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java new file mode 100644 index 0000000..9cba8a0 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Set; + +/** + * A {@link DelegateClassAdapter} can transform some methods from a class into + * delegates that defer the call to an associated delegate class. + * <p/> + * This is used to override specific methods and or all native methods in classes. + */ +public class DelegateClassAdapter extends ClassAdapter { + + public final static String ALL_NATIVES = "<<all_natives>>"; + + private final String mClassName; + private final Set<String> mDelegateMethods; + private final Log mLog; + + /** + * Creates a new {@link DelegateClassAdapter} that can transform some methods + * from a class into delegates that defer the call to an associated delegate class. + * <p/> + * This is used to override specific methods and or all native methods in classes. + * + * @param log The logger object. Must not be null. + * @param cv the class visitor to which this adapter must delegate calls. + * @param className The internal class name of the class to visit, + * e.g. <code>com/android/SomeClass$InnerClass</code>. + * @param delegateMethods The set of method names to modify and/or the + * special constant {@link #ALL_NATIVES} to convert all native methods. + */ + public DelegateClassAdapter(Log log, + ClassVisitor cv, + String className, + Set<String> delegateMethods) { + super(cv); + mLog = log; + mClassName = className; + mDelegateMethods = delegateMethods; + } + + //---------------------------------- + // Methods from the ClassAdapter + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + + boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; + + boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || + mDelegateMethods.contains(name); + + if (useDelegate) { + // remove native + access = access & ~Opcodes.ACC_NATIVE; + } + + MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); + if (useDelegate) { + DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName, + name, desc, isStatic); + if (isNative) { + // A native has no code to visit, so we need to generate it directly. + a.generateCode(); + } else { + return a; + } + } + return mw; + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java new file mode 100644 index 0000000..8d7f016 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2008 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 com.android.tools.layoutlib.create; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.ArrayList; + +/** + * This method adapter rewrites a method by discarding the original code and generating + * a call to a delegate. Original annotations are passed along unchanged. + * <p/> + * Calls are delegated to a class named <code><className>_Delegate</code> with + * static methods matching the methods to be overridden here. The methods have the + * same return type. The argument type list is the same except the "this" reference is + * passed first for non-static methods. + * <p/> + * A new annotation is added. + * <p/> + * Note that native methods have, by definition, no code so there's nothing a visitor + * can visit. That means the caller must call {@link #generateCode()} directly for + * a native and use the visitor pattern for non-natives. + * <p/> + * Instances of this class are not re-usable. You need a new instance for each method. + */ +class DelegateMethodAdapter implements MethodVisitor { + + /** + * Suffix added to delegate classes. + */ + public static final String DELEGATE_SUFFIX = "_Delegate"; + + private static String CONSTRUCTOR = "<init>"; + private static String CLASS_INIT = "<clinit>"; + + /** The parent method writer */ + private MethodVisitor mParentVisitor; + /** Flag to output the first line number. */ + private boolean mOutputFirstLineNumber = true; + /** The original method descriptor (return type + argument types.) */ + private String mDesc; + /** True if the original method is static. */ + private final boolean mIsStatic; + /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ + private final String mClassName; + /** The method name. */ + private final String mMethodName; + /** Logger object. */ + private final Log mLog; + /** True if {@link #visitCode()} has been invoked. */ + private boolean mVisitCodeCalled; + + /** + * Creates a new {@link DelegateMethodAdapter} that will transform this method + * into a delegate call. + * <p/> + * See {@link DelegateMethodAdapter} for more details. + * + * @param log The logger object. Must not be null. + * @param mv the method visitor to which this adapter must delegate calls. + * @param className The internal class name of the class to visit, + * e.g. <code>com/android/SomeClass$InnerClass</code>. + * @param methodName The simple name of the method. + * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} + + * {@link Type#getArgumentTypes(String)}) + * @param isStatic True if the method is declared static. + */ + public DelegateMethodAdapter(Log log, + MethodVisitor mv, + String className, + String methodName, + String desc, + boolean isStatic) { + mLog = log; + mParentVisitor = mv; + mClassName = className; + mMethodName = methodName; + mDesc = desc; + mIsStatic = isStatic; + + if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) { + // We're going to simplify by not supporting constructors. + // The only trick with a constructor is to find the proper super constructor + // and call it (and deciding if we should mirror the original method call to + // a custom constructor or call a default one.) + throw new UnsupportedOperationException( + String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", + className, methodName, desc)); + } + } + + /** + * Generates the new code for the method. + * <p/> + * For native methods, this must be invoked directly by {@link DelegateClassAdapter} + * (since they have no code to visit). + * <p/> + * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to + * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern + * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then + * this method will be invoked from {@link MethodVisitor#visitEnd()}. + */ + public void generateCode() { + /* + * The goal is to generate a call to a static delegate method. + * If this method is non-static, the first parameter will be 'this'. + * All the parameters must be passed and then the eventual return type returned. + * + * Example, let's say we have a method such as + * public void method_1(int a, Object b, ArrayList<String> c) { ... } + * + * We'll want to create a body that calls a delegate method like this: + * TheClass_Delegate.method_1(this, a, b, c); + * + * If the method is non-static and the class name is an inner class (e.g. has $ in its + * last segment), we want to push the 'this' of the outer class first: + * OuterClass_InnerClass_Delegate.method_1( + * OuterClass.this, + * OuterClass$InnerClass.this, + * a, b, c); + * + * Only one level of inner class is supported right now, for simplicity and because + * we don't need more. + * + * The generated class name is the current class name with "_Delegate" appended to it. + * One thing to realize is that we don't care about generics -- since generic types + * are erased at runtime, they have no influence on the method name being called. + */ + + // Add our annotation + AnnotationVisitor aw = mParentVisitor.visitAnnotation( + Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), + true); // visible at runtime + aw.visitEnd(); + + if (!mVisitCodeCalled) { + // If this is a direct call to generateCode() as done by DelegateClassAdapter + // for natives, visitCode() hasn't been called yet. + mParentVisitor.visitCode(); + mVisitCodeCalled = true; + } + + ArrayList<Type> paramTypes = new ArrayList<Type>(); + String delegateClassName = mClassName + DELEGATE_SUFFIX; + boolean pushedArg0 = false; + int maxStack = 0; + + // For an instance method (e.g. non-static), push the 'this' preceded + // by the 'this' of any outer class, if any. + if (!mIsStatic) { + // Check if the last segment of the class name has inner an class. + // Right now we only support one level of inner classes. + int slash = mClassName.lastIndexOf('/'); + int dol = mClassName.lastIndexOf('$'); + if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { + String outerClass = mClassName.substring(0, dol); + Type outerType = Type.getObjectType(outerClass); + + // Change a delegate class name to "com/foo/Outer_Inner_Delegate" + delegateClassName = delegateClassName.replace('$', '_'); + + // The first-level inner class has a package-protected member called 'this$0' + // that points to the outer class. + + // Push this.getField("this$0") on the call stack. + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this + mParentVisitor.visitFieldInsn(Opcodes.GETFIELD, + mClassName, // class where the field is defined + "this$0", // field name + outerType.getDescriptor()); // type of the field + maxStack++; + paramTypes.add(outerType); + } + + // Push "this" for the instance method, which is always ALOAD 0 + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); + maxStack++; + pushedArg0 = true; + paramTypes.add(Type.getObjectType(mClassName)); + } + + // Push all other arguments. Start at arg 1 if we already pushed 'this' above. + Type[] argTypes = Type.getArgumentTypes(mDesc); + int maxLocals = pushedArg0 ? 1 : 0; + for (Type t : argTypes) { + int size = t.getSize(); + mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); + maxLocals += size; + maxStack += size; + paramTypes.add(t); + } + + // Construct the descriptor of the delegate based on the parameters + // we pushed on the call stack. The return type remains unchanged. + String desc = Type.getMethodDescriptor( + Type.getReturnType(mDesc), + paramTypes.toArray(new Type[paramTypes.size()])); + + // Invoke the static delegate + mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, + delegateClassName, + mMethodName, + desc); + + Type returnType = Type.getReturnType(mDesc); + mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); + + mParentVisitor.visitMaxs(maxStack, maxLocals); + mParentVisitor.visitEnd(); + + // For debugging now. Maybe we should collect these and store them in + // a text file for helping create the delegates. We could also compare + // the text file to a golden and break the build on unsupported changes + // or regressions. Even better we could fancy-print something that looks + // like the expected Java method declaration. + mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc); + } + + /* Pass down to visitor writer. In this implementation, either do nothing. */ + public void visitCode() { + mVisitCodeCalled = true; + mParentVisitor.visitCode(); + } + + /* + * visitMaxs is called just before visitEnd if there was any code to rewrite. + * Skip the original. + */ + public void visitMaxs(int maxStack, int maxLocals) { + } + + /** + * End of visiting. Generate the messaging code. + */ + public void visitEnd() { + generateCode(); + } + + /* Writes all annotation from the original method. */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return mParentVisitor.visitAnnotation(desc, visible); + } + + /* Writes all annotation default values from the original method. */ + public AnnotationVisitor visitAnnotationDefault() { + return mParentVisitor.visitAnnotationDefault(); + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, + boolean visible) { + return mParentVisitor.visitParameterAnnotation(parameter, desc, visible); + } + + /* Writes all attributes from the original method. */ + public void visitAttribute(Attribute attr) { + mParentVisitor.visitAttribute(attr); + } + + /* + * Only writes the first line number present in the original code so that source + * viewers can direct to the correct method, even if the content doesn't match. + */ + public void visitLineNumber(int line, Label start) { + if (mOutputFirstLineNumber) { + mParentVisitor.visitLineNumber(line, start); + mOutputFirstLineNumber = false; + } + } + + public void visitInsn(int opcode) { + // Skip original code. + } + + public void visitLabel(Label label) { + // Skip original code. + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + // Skip original code. + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + // Skip original code. + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + // Skip original code. + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + // Skip original code. + } + + public void visitIincInsn(int var, int increment) { + // Skip original code. + } + + public void visitIntInsn(int opcode, int operand) { + // Skip original code. + } + + public void visitJumpInsn(int opcode, Label label) { + // Skip original code. + } + + public void visitLdcInsn(Object cst) { + // Skip original code. + } + + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + // Skip original code. + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + // Skip original code. + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + // Skip original code. + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + // Skip original code. + } + + public void visitTypeInsn(int opcode, String type) { + // Skip original code. + } + + public void visitVarInsn(int opcode, int var) { + // Skip original code. + } + +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java new file mode 100644 index 0000000..40c1706 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create; + +/** + * Interface describing the work to be done by {@link AsmGenerator}. + */ +public interface ICreateInfo { + + /** + * Returns the list of class from layoutlib_create to inject in layoutlib. + * The list can be empty but must not be null. + */ + public abstract Class<?>[] getInjectedClasses(); + + /** + * Returns the list of methods to rewrite as delegates. + * The list can be empty but must not be null. + */ + public abstract String[] getDelegateMethods(); + + /** + * Returns the list of classes on which to delegate all native methods. + * The list can be empty but must not be null. + */ + public abstract String[] getDelegateClassNatives(); + + /** + * Returns The list of methods to stub out. Each entry must be in the form + * "package.package.OuterClass$InnerClass#MethodName". + * The list can be empty but must not be null. + */ + public abstract String[] getOverriddenMethods(); + + /** + * Returns the list of classes to rename, must be an even list: the binary FQCN + * of class to replace followed by the new FQCN. + * The list can be empty but must not be null. + */ + public abstract String[] getRenamedClasses(); + + /** + * Returns the list of classes for which the methods returning them should be deleted. + * The array contains a list of null terminated section starting with the name of the class + * to rename in which the methods are deleted, followed by a list of return types identifying + * the methods to delete. + * The list can be empty but must not be null. + */ + public abstract String[] getDeleteReturns(); + +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index b30e9e5..0fce7ef 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -21,7 +21,28 @@ import java.util.ArrayList; import java.util.Set; - +/** + * Entry point for the layoutlib_create tool. + * <p/> + * The tool does not currently rely on any external configuration file. + * Instead the configuration is mostly done via the {@link CreateInfo} class. + * <p/> + * For a complete description of the tool and its implementation, please refer to + * the "README.txt" file at the root of this project. + * <p/> + * For a quick test, invoke this as follows: + * <pre> + * $ make layoutlib + * </pre> + * which does: + * <pre> + * $ make layoutlib_create <bunch of framework jars> + * $ out/host/linux-x86/framework/bin/layoutlib_create \ + * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \ + * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \ + * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar + * </pre> + */ public class Main { public static void main(String[] args) { @@ -42,15 +63,12 @@ public class Main { } try { - AsmGenerator agen = new AsmGenerator(log, osDestJar[0], - CreateInfo.INJECTED_CLASSES, - CreateInfo.OVERRIDDEN_METHODS, - CreateInfo.RENAMED_CLASSES, - CreateInfo.REMOVED_METHODS - ); + AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo()); AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen, - new String[] { "android.view.View" }, // derived from + new String[] { // derived from + "android.view.View", + }, new String[] { // include classes "android.*", // for android.R "android.util.*", diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java index e294d56..f2d9755 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java @@ -26,7 +26,7 @@ import org.objectweb.asm.Type; import java.util.Set; /** - * Class adapter that can stub some or all of the methods of the class. + * Class adapter that can stub some or all of the methods of the class. */ class TransformClassAdapter extends ClassAdapter { @@ -41,12 +41,12 @@ class TransformClassAdapter extends ClassAdapter { /** * Creates a new class adapter that will stub some or all methods. - * @param logger - * @param stubMethods + * @param logger + * @param stubMethods list of method signatures to always stub out * @param deleteReturns list of types that trigger the deletion of methods returning them. * @param className The name of the class being modified * @param cv The parent class writer visitor - * @param stubNativesOnly True if only native methods should be stubbed. False if all + * @param stubNativesOnly True if only native methods should be stubbed. False if all * methods should be stubbed. * @param hasNative True if the method has natives, in which case its access should be * changed. @@ -67,10 +67,10 @@ class TransformClassAdapter extends ClassAdapter { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - + // This class might be being renamed. name = mClassName; - + // remove protected or private and set as public access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); access |= Opcodes.ACC_PUBLIC; @@ -82,7 +82,7 @@ class TransformClassAdapter extends ClassAdapter { mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0); super.visit(version, access, name, signature, superName, interfaces); } - + /* Visits the header of an inner class. */ @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { @@ -101,7 +101,7 @@ class TransformClassAdapter extends ClassAdapter { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - + if (mDeleteReturns != null) { Type t = Type.getReturnType(desc); if (t.getSort() == Type.OBJECT) { @@ -130,16 +130,16 @@ class TransformClassAdapter extends ClassAdapter { (mStubAll || (access & Opcodes.ACC_NATIVE) != 0) || mStubMethods.contains(methodSignature)) { - + boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; // remove abstract, final and native access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE); - + String invokeSignature = methodSignature + desc; mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : ""); - + MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature, isStatic, isNative); @@ -149,7 +149,7 @@ class TransformClassAdapter extends ClassAdapter { return super.visitMethod(access, name, desc, signature, exceptions); } } - + /* Visits a field. Makes it public. */ @Override public FieldVisitor visitField(int access, String name, String desc, String signature, @@ -157,7 +157,7 @@ class TransformClassAdapter extends ClassAdapter { // change access to public access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); access |= Opcodes.ACC_PUBLIC; - + return super.visitField(access, name, desc, signature, value); } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 603284e..d6dba6a 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor; -import com.android.tools.layoutlib.create.LogTest.MockLog; import org.junit.After; import org.junit.Before; @@ -46,9 +45,9 @@ public class AsmAnalyzerTest { @Before public void setUp() throws Exception { - mLog = new LogTest.MockLog(); + mLog = new MockLog(); URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar"); - + mOsJarPath = new ArrayList<String>(); mOsJarPath.add(url.getFile()); @@ -69,9 +68,9 @@ public class AsmAnalyzerTest { "mock_android.dummy.InnerTest$DerivingClass", "mock_android.dummy.InnerTest$MyGenerics1", "mock_android.dummy.InnerTest$MyIntEnum", - "mock_android.dummy.InnerTest$MyStaticInnerClass", - "mock_android.dummy.InnerTest$NotStaticInner1", - "mock_android.dummy.InnerTest$NotStaticInner2", + "mock_android.dummy.InnerTest$MyStaticInnerClass", + "mock_android.dummy.InnerTest$NotStaticInner1", + "mock_android.dummy.InnerTest$NotStaticInner2", "mock_android.view.View", "mock_android.view.ViewGroup", "mock_android.view.ViewGroup$LayoutParams", @@ -83,7 +82,7 @@ public class AsmAnalyzerTest { }, map.keySet().toArray()); } - + @Test public void testFindClass() throws IOException, LogAbortException { Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); @@ -91,7 +90,7 @@ public class AsmAnalyzerTest { ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams", zipClasses, found); - + assertNotNull(cr); assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName()); assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" }, @@ -172,14 +171,14 @@ public class AsmAnalyzerTest { "mock_android.widget.TableLayout", }, found.keySet().toArray()); - + for (String key : found.keySet()) { ClassReader value = found.get(key); assertNotNull("No value for " + key, value); assertEquals(key, AsmAnalyzer.classReaderToClassName(value)); } } - + @Test public void testDependencyVisitor() throws IOException, LogAbortException { Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); @@ -190,7 +189,7 @@ public class AsmAnalyzerTest { ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep); DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps); - + // get first level dependencies cr.accept(visitor, 0 /* flags */); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 7cdf79a..f4ff389 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -20,8 +20,6 @@ package com.android.tools.layoutlib.create; import static org.junit.Assert.assertArrayEquals; -import com.android.tools.layoutlib.create.LogTest.MockLog; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -44,9 +42,9 @@ public class AsmGeneratorTest { @Before public void setUp() throws Exception { - mLog = new LogTest.MockLog(); + mLog = new MockLog(); URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar"); - + mOsJarPath = new ArrayList<String>(); mOsJarPath.add(url.getFile()); @@ -65,16 +63,41 @@ public class AsmGeneratorTest { @Test public void testClassRenaming() throws IOException, LogAbortException { - - AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, - null, // classes to inject in the final JAR - null, // methods to force override - new String[] { // classes to rename (so that we can replace them) - "mock_android.view.View", "mock_android.view._Original_View", - "not.an.actual.ClassName", "anoter.fake.NewClassName", - }, - null // methods deleted from their return type. - ); + + ICreateInfo ci = new ICreateInfo() { + public Class<?>[] getInjectedClasses() { + // classes to inject in the final JAR + return new Class<?>[0]; + } + + public String[] getDelegateMethods() { + return new String[0]; + } + + public String[] getDelegateClassNatives() { + return new String[0]; + } + + public String[] getOverriddenMethods() { + // methods to force override + return new String[0]; + } + + public String[] getRenamedClasses() { + // classes to rename (so that we can replace them) + return new String[] { + "mock_android.view.View", "mock_android.view._Original_View", + "not.an.actual.ClassName", "anoter.fake.NewClassName", + }; + } + + public String[] getDeleteReturns() { + // methods deleted from their return type. + return new String[0]; + } + }; + + AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, null, // derived from @@ -83,7 +106,7 @@ public class AsmGeneratorTest { }); aa.analyze(); agen.generate(); - + Set<String> notRenamed = agen.getClassesNotRenamed(); assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray()); } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java index d6916ae..0135c40 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java @@ -33,8 +33,9 @@ public class ClassHasNativeVisitorTest { @Test public void testHasNative() throws IOException { MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor(); - ClassReader cr = new ClassReader( - "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative"); + String className = + this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName(); + ClassReader cr = new ClassReader(className); cr.accept(cv, 0 /* flags */); assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound()); @@ -44,14 +45,17 @@ public class ClassHasNativeVisitorTest { @Test public void testHasNoNative() throws IOException { MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor(); - ClassReader cr = new ClassReader( - "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative"); + String className = + this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName(); + ClassReader cr = new ClassReader(className); cr.accept(cv, 0 /* flags */); assertArrayEquals(new String[0], cv.getMethodsFound()); assertFalse(cv.hasNativeMethods()); } + //------- + /** * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found. */ diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java new file mode 100644 index 0000000..e8b3ea8 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tools.layoutlib.create.dataclass.ClassWithNative; +import com.android.tools.layoutlib.create.dataclass.OuterClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class DelegateClassAdapterTest { + + private MockLog mLog; + + private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName(); + private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); + private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + + InnerClass.class.getSimpleName(); + + @Before + public void setUp() throws Exception { + mLog = new MockLog(); + mLog.setVerbose(true); // capture debug error too + } + + /** + * Tests that a class not being modified still works. + */ + @SuppressWarnings("unchecked") + @Test + public void testNoOp() throws Throwable { + // create an instance of the class that will be modified + // (load the class in a distinct class loader so that we can trash its definition later) + ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { }; + Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME); + ClassWithNative instance1 = clazz1.newInstance(); + assertEquals(42, instance1.add(20, 22)); + try { + instance1.callNativeInstance(10, 3.1415, new Object[0] ); + fail("Test should have failed to invoke callTheNativeMethod [1]"); + } catch (UnsatisfiedLinkError e) { + // This is expected to fail since the native method is not implemented. + } + + // Now process it but tell the delegate to not modify any method + ClassWriter cw = new ClassWriter(0 /*flags*/); + + HashSet<String> delegateMethods = new HashSet<String>(); + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cv = new DelegateClassAdapter( + mLog, cw, internalClassName, delegateMethods); + + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + + // Load the generated class in a different class loader and try it again + + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); + Object i2 = clazz2.newInstance(); + assertNotNull(i2); + assertEquals(42, callAdd(i2, 20, 22)); + + try { + callCallNativeInstance(i2, 10, 3.1415, new Object[0]); + fail("Test should have failed to invoke callTheNativeMethod [2]"); + } catch (InvocationTargetException e) { + // This is expected to fail since the native method has NOT been + // overridden here. + assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass()); + } + + // Check that the native method does NOT have the new annotation + Method[] m = clazz2.getDeclaredMethods(); + assertEquals("native_instance", m[2].getName()); + assertTrue(Modifier.isNative(m[2].getModifiers())); + Annotation[] a = m[2].getAnnotations(); + assertEquals(0, a.length); + } + }; + cl2.add(NATIVE_CLASS_NAME, cw); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + /** + * {@link DelegateMethodAdapter} does not support overriding constructors yet, + * so this should fail with an {@link UnsupportedOperationException}. + * + * Although not tested here, the message of the exception should contain the + * constructor signature. + */ + @Test(expected=UnsupportedOperationException.class) + public void testConstructorsNotSupported() throws IOException { + ClassWriter cw = new ClassWriter(0 /*flags*/); + + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); + + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("<init>"); + DelegateClassAdapter cv = new DelegateClassAdapter( + mLog, cw, internalClassName, delegateMethods); + + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + } + + @Test + public void testDelegateNative() throws Throwable { + ClassWriter cw = new ClassWriter(0 /*flags*/); + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); + + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add(DelegateClassAdapter.ALL_NATIVES); + DelegateClassAdapter cv = new DelegateClassAdapter( + mLog, cw, internalClassName, delegateMethods); + + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); + cr.accept(cv, 0 /* flags */); + + // Load the generated class in a different class loader and try it + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); + Object i2 = clazz2.newInstance(); + assertNotNull(i2); + + // Use reflection to access inner methods + assertEquals(42, callAdd(i2, 20, 22)); + + Object[] objResult = new Object[] { null }; + int result = callCallNativeInstance(i2, 10, 3.1415, objResult); + assertEquals((int)(10 + 3.1415), result); + assertSame(i2, objResult[0]); + + // Check that the native method now has the new annotation and is not native + Method[] m = clazz2.getDeclaredMethods(); + assertEquals("native_instance", m[2].getName()); + assertFalse(Modifier.isNative(m[2].getModifiers())); + Annotation[] a = m[2].getAnnotations(); + assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName()); + } + }; + cl2.add(NATIVE_CLASS_NAME, cw); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + @Test + public void testDelegateInner() throws Throwable { + // We'll delegate the "get" method of both the inner and outer class. + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // The original Outer.get returns 1+10+20, + // but the delegate makes it return 4+10+20 + assertEquals(4+10+20, callGet(o2, 10, 20)); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor( + new Class<?>[] { outerClazz2 }); + Object i2 = innerCons.newInstance(new Object[] { o2 }); + assertNotNull(i2); + + // The original Inner.get returns 3+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + //------- + + /** + * A class loader than can define and instantiate our modified classes. + * <p/> + * The trick here is that this class loader will test our <em>modified</em> version + * of the classes, the one with the delegate calls. + * <p/> + * Trying to do so in the original class loader generates all sort of link issues because + * there are 2 different definitions of the same class name. This class loader will + * define and load the class when requested by name and provide helpers to access the + * instance methods via reflection. + */ + private abstract class ClassLoader2 extends ClassLoader { + + private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>(); + + public ClassLoader2() { + super(null); + } + + public ClassLoader2 add(String className, byte[] definition) { + mClassDefs.put(className, definition); + return this; + } + + public ClassLoader2 add(String className, ClassWriter rewrittenClass) { + mClassDefs.put(className, rewrittenClass.toByteArray()); + return this; + } + + private Set<Entry<String, byte[]>> getByteCode() { + return mClassDefs.entrySet(); + } + + @SuppressWarnings("unused") + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + try { + return super.findClass(name); + } catch (ClassNotFoundException e) { + + byte[] def = mClassDefs.get(name); + if (def != null) { + // Load the modified ClassWithNative from its bytes representation. + return defineClass(name, def, 0, def.length); + } + + try { + // Load everything else from the original definition into the new class loader. + ClassReader cr = new ClassReader(name); + ClassWriter cw = new ClassWriter(0); + cr.accept(cw, 0); + byte[] bytes = cw.toByteArray(); + return defineClass(name, bytes, 0, bytes.length); + + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } + + /** + * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection. + */ + public int callGet(Object instance, int a, long b) throws Exception { + Method m = instance.getClass().getMethod("get", + new Class<?>[] { int.class, long.class } ); + + Object result = m.invoke(instance, new Object[] { a, b }); + return ((Integer) result).intValue(); + } + + /** + * Accesses {@link ClassWithNative#add(int, int)} via reflection. + */ + public int callAdd(Object instance, int a, int b) throws Exception { + Method m = instance.getClass().getMethod("add", + new Class<?>[] { int.class, int.class }); + + Object result = m.invoke(instance, new Object[] { a, b }); + return ((Integer) result).intValue(); + } + + /** + * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])} + * via reflection. + */ + public int callCallNativeInstance(Object instance, int a, double d, Object[] o) + throws Exception { + Method m = instance.getClass().getMethod("callNativeInstance", + new Class<?>[] { int.class, double.class, Object[].class }); + + Object result = m.invoke(instance, new Object[] { a, d, o }); + return ((Integer) result).intValue(); + } + + public abstract void testModifiedInstance() throws Exception; + } + + /** + * For debugging, it's useful to dump the content of the generated classes + * along with the exception that was generated. + * + * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor + * class and associated utilities which are found in the ASM source jar. Since we don't + * want that dependency in the source code, we only put it manually for development and + * access the TraceClassVisitor via reflection if present. + * + * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()} + * @param cl2 The {@link ClassLoader2} instance with the generated bytecode. + * @return Either original {@code t} or a new wrapper {@link Throwable} + */ + private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) { + try { + // For debugging, dump the bytecode of the class in case of unexpected error + // if we can find the TraceClassVisitor class. + Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor"); + + StringBuilder sb = new StringBuilder(); + sb.append('\n').append(t.getClass().getCanonicalName()); + if (t.getMessage() != null) { + sb.append(": ").append(t.getMessage()); + } + + for (Entry<String, byte[]> entry : cl2.getByteCode()) { + String className = entry.getKey(); + byte[] bytes = entry.getValue(); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); + Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() }); + Object tcv = cons.newInstance(new Object[] { pw }); + ClassReader cr2 = new ClassReader(bytes); + cr2.accept((ClassVisitor) tcv, 0 /* flags */); + + sb.append("\nBytecode dump: <").append(className).append(">:\n") + .append(sw.toString()); + } + + // Re-throw exception with new message + RuntimeException ex = new RuntimeException(sb.toString(), t); + return ex; + } catch (Throwable ignore) { + // In case of problem, just throw the original exception as-is. + return t; + } + } + +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java index 3f13158..1a5f653 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java @@ -24,33 +24,8 @@ import org.junit.Test; public class LogTest { - public static class MockLog extends Log { - StringBuilder mOut = new StringBuilder(); - StringBuilder mErr = new StringBuilder(); - - public String getOut() { - return mOut.toString(); - } - - public String getErr() { - return mErr.toString(); - } - - @Override - protected void outPrintln(String msg) { - mOut.append(msg); - mOut.append('\n'); - } - - @Override - protected void errPrintln(String msg) { - mErr.append(msg); - mErr.append('\n'); - } - } - private MockLog mLog; - + @Before public void setUp() throws Exception { mLog = new MockLog(); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java new file mode 100644 index 0000000..de750a3 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create; + + +public class MockLog extends Log { + StringBuilder mOut = new StringBuilder(); + StringBuilder mErr = new StringBuilder(); + + public String getOut() { + return mOut.toString(); + } + + public String getErr() { + return mErr.toString(); + } + + @Override + protected void outPrintln(String msg) { + mOut.append(msg); + mOut.append('\n'); + } + + @Override + protected void errPrintln(String msg) { + mErr.append(msg); + mErr.append('\n'); + } +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java new file mode 100644 index 0000000..c314853 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Dummy test class with a native method. + * The native method is not defined and any attempt to invoke it will + * throw an {@link UnsatisfiedLinkError}. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class ClassWithNative { + public ClassWithNative() { + } + + public int add(int a, int b) { + return a + b; + } + + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + + public int callNativeInstance(int a, double d, Object[] o) { + return native_instance(a, d, o); + } + + private native int native_instance(int a, double d, Object[] o); +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java new file mode 100644 index 0000000..a3d4dc6 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 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 com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class ClassWithNative_Delegate { + public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) { + if (o != null && o.length > 0) { + o[0] = instance; + } + return (int)(a + d); + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java new file mode 100644 index 0000000..9dc2f69 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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 com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Test class with an inner class. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass { + private int mOuterValue = 1; + public OuterClass() { + } + + // Outer.get returns 1 + a + b + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + public int get(int a, long b) { + return mOuterValue + a + (int) b; + } + + public class InnerClass { + public InnerClass() { + } + + // Inner.get returns 1+2=3 + a + b + public int get(int a, long b) { + return 2 + mOuterValue + a + (int) b; + } + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java new file mode 100644 index 0000000..3252d87 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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 com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_Delegate { + // The delegate override of Outer.get returns 4 + a + b + public static int get(OuterClass instance, int a, long b) { + return 4 + a + (int) b; + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java new file mode 100644 index 0000000..b472220 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 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 com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_InnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(OuterClass outer, InnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |