diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/app/admin/DeviceAdminReceiver.java | 53 | ||||
| -rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 49 | ||||
| -rw-r--r-- | core/java/android/app/admin/IDevicePolicyManager.aidl | 7 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 2 | ||||
| -rw-r--r-- | core/java/android/text/StaticLayout.java | 355 |
5 files changed, 246 insertions, 220 deletions
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 15def09..d87491f 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.security.KeyChain; /** * Base class for implementing a device administration component. This @@ -223,7 +224,28 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE"; - /** + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.ACTION_CHOOSE_PRIVATE_KEY_ALIAS"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID = "android.app.extra.CHOOSE_PRIVATE_KEY_SENDER_UID"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_HOST = "android.app.extra.CHOOSE_PRIVATE_KEY_HOST"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_PORT = "android.app.extra.CHOOSE_PRIVATE_KEY_PORT"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_URL = "android.app.extra.CHOOSE_PRIVATE_KEY_URL"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.extra.CHOOSE_PRIVATE_KEY_ALIAS"; + + /** @hide */ + public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE"; + + /** * Name under which a DevicePolicy component publishes information * about itself. This meta-data must reference an XML resource containing * a device-admin tag. XXX TO DO: describe syntax. @@ -403,6 +425,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Allows this receiver to select the alias for a private key and certificate pair for + * authentication. If this method returns null, the default {@link android.app.Activity} will be + * shown that lets the user pick a private key and certificate pair. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param uid The uid asking for the private key and certificate pair. + * @param host The authentication host, may be null. + * @param port The authentication post, may be null. + * @param url The URL to authenticate, may be null. + * @param alias The alias preselected by the client, or null. + * @return The private key alias to return and grant access to. + * @see KeyChain#choosePrivateKeyAlias + */ + public String onChoosePrivateKeyAlias(Context context, Intent intent, long uid, String host, + int port, String url, String alias) { + return null; + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -431,6 +473,15 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onPasswordExpiring(context, intent); } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) { onProfileProvisioningComplete(context, intent); + } else if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { + long uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, 0); + String host = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_HOST); + int port = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_PORT, 0); + String url = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_URL); + String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS); + String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, host, port, url, + alias); + setResultData(chosenAlias); } else if (ACTION_LOCK_TASK_ENTERING.equals(action)) { String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE); onLockTaskModeEntering(context, intent, pkg); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 41bbb87..e63b164 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.RestrictionsManager; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -38,6 +39,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.Credentials; import android.service.restrictions.RestrictionsReceiver; import android.util.Log; @@ -49,6 +51,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.security.PrivateKey; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -1821,6 +1825,31 @@ public class DevicePolicyManager { } /** + * Silently installs a certificate and private key pair. + * + * @param who Which {@link DeviceAdminReceiver} this request is associated with. + * @param privKey The private key to install. + * @param cert The certificate to install. + * @param alias The private key alias under which to install the certificate. If a certificate + * with that alias already exists, it will be overwritten. + */ + public void installKeyPair(ComponentName who, PrivateKey privKey, Certificate cert, + String alias) { + try { + Log.d(TAG, "PEM: " + Credentials.convertToPem(cert)); + Log.d(TAG, "Encoded: " + cert.getEncoded()); + mService.installKeyPair(who, privKey.getEncoded(), Credentials.convertToPem(cert), + alias); + } catch (CertificateException e) { + Log.w(TAG, "Error encoding certificate", e); + } catch (IOException e) { + Log.w(TAG, "Error writing certificate", e); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + + /** * Returns the alias of a given CA certificate in the certificate store, or null if it * doesn't exist. */ @@ -2514,6 +2543,26 @@ public class DevicePolicyManager { } /** + * Called by a profile owner to register a listener for private key access. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param listener The {@link DeviceAdminReceiver} that should listen for private key access + * requests. + * @see DeviceAdminReceiver#onChoosePrivateKeyAlias + */ + public void registerPrivateKeyAccessListener(ComponentName admin, ComponentName listener) { + if (mService != null) { + try { + mService.registerPrivateKeyAccessListener(admin, listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed to register private key access listener with device policy " + + "service", e); + } + } + } + + /** + * Sets a list of features to enable for a TrustAgentService component. This is meant to be * Sets a list of features to enable for a TrustAgent component. This is meant to be * used in conjunction with {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, which will disable all * trust agents but those with features enabled by this function call. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1e17bb6..5e63055 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,7 @@ package android.app.admin; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -125,6 +126,12 @@ interface IDevicePolicyManager { boolean installCaCert(in ComponentName admin, in byte[] certBuffer); void uninstallCaCert(in ComponentName admin, in String alias); + void installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer, String alias); + + void registerPrivateKeyAccessListener(in ComponentName who, in ComponentName admin); + + void choosePrivateKeyAlias(in PendingIntent sender, in String host, int port, in String url, in String alias, IBinder aliasCallback); + void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2241716..7f56f2a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -305,7 +305,7 @@ public final class Settings { /** * Activity Action: Show settings to allow configuration of - * cast endpoints. + * {@link android.media.routing.MediaRouteService media route providers}. * <p> * In some cases, a matching Activity may not exist, so ensure you * safeguard against this. diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index aecf488..22a26eb 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import java.util.Arrays; + /** * StaticLayout is a Layout for text that will not be edited after it * is laid out. Use {@link DynamicLayout} for text that may change. @@ -161,7 +163,12 @@ public class StaticLayout extends Layout { float spacingadd, boolean includepad, boolean trackpad, float ellipsizedWidth, TextUtils.TruncateAt ellipsize) { - int[] breakOpp = null; + LineBreaks lineBreaks = new LineBreaks(); + // store span end locations + int[] spanEndCache = new int[4]; + // store fontMetrics per span range + // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range) + int[] fmCache = new int[4 * 4]; final String localeLanguageTag = paint.getTextLocale().toLanguageTag(); mLineCount = 0; @@ -186,7 +193,7 @@ public class StaticLayout extends Layout { else paraEnd++; - int firstWidthLineLimit = mLineCount + 1; + int firstWidthLineCount = 1; int firstWidth = outerWidth; int restWidth = outerWidth; @@ -204,9 +211,8 @@ public class StaticLayout extends Layout { // leading margin spans, not just this particular one if (lms instanceof LeadingMarginSpan2) { LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; - int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2)); - firstWidthLineLimit = Math.max(firstWidthLineLimit, - lmsFirstLine + lms2.getLeadingMarginLineCount()); + firstWidthLineCount = Math.max(firstWidthLineCount, + lms2.getLeadingMarginLineCount()); } } @@ -242,32 +248,23 @@ public class StaticLayout extends Layout { int dir = measured.mDir; boolean easy = measured.mEasy; - breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp); - int breakOppIndex = 0; - - int width = firstWidth; - - float w = 0; - // here is the offset of the starting character of the line we are currently measuring - int here = paraStart; - - // ok is a character offset located after a word separator (space, tab, number...) where - // we would prefer to cut the current line. Equals to here when no such break was found. - int ok = paraStart; - float okWidth = w; - int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0; - - // fit is a character offset such that the [here, fit[ range fits in the allowed width. - // We will cut the line there if no ok position is found. - int fit = paraStart; - float fitWidth = w; - int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0; - - boolean hasTabOrEmoji = false; - boolean hasTab = false; - TabStops tabStops = null; - + // measurement has to be done before performing line breaking + // but we don't want to recompute fontmetrics or span ranges the + // second time, so we cache those and then use those stored values + int fmCacheCount = 0; + int spanEndCacheCount = 0; for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + if (fmCacheCount * 4 >= fmCache.length) { + int[] grow = new int[fmCacheCount * 4 * 2]; + System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); + fmCache = grow; + } + + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } if (spanned == null) { spanEnd = paraEnd; @@ -283,197 +280,109 @@ public class StaticLayout extends Layout { measured.addStyleRun(paint, spans, spanLen, fm); } - int fmTop = fm.top; - int fmBottom = fm.bottom; - int fmAscent = fm.ascent; - int fmDescent = fm.descent; - - for (int j = spanStart; j < spanEnd; j++) { - char c = chs[j - paraStart]; - - if (c == CHAR_NEW_LINE) { - // intentionally left empty - } else if (c == CHAR_TAB) { - if (hasTab == false) { - hasTab = true; - hasTabOrEmoji = true; - if (spanned != null) { - // First tab this para, check for tabstops - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - tabStops = new TabStops(TAB_INCREMENT, spans); - } - } - } - if (tabStops != null) { - w = tabStops.nextTab(w); - } else { - w = TabStops.nextDefaultStop(w, TAB_INCREMENT); - } - } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE - && j + 1 < spanEnd) { - int emoji = Character.codePointAt(chs, j - paraStart); - - if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { - Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji); - - if (bm != null) { - Paint whichPaint; - - if (spanned == null) { - whichPaint = paint; - } else { - whichPaint = mWorkPaint; - } - - float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight(); - - w += wid; - hasTabOrEmoji = true; - j++; - } else { - w += widths[j - paraStart]; - } - } else { - w += widths[j - paraStart]; - } - } else { - w += widths[j - paraStart]; + // the order of storage here (top, bottom, ascent, descent) has to match the code below + // where these values are retrieved + fmCache[fmCacheCount * 4 + 0] = fm.top; + fmCache[fmCacheCount * 4 + 1] = fm.bottom; + fmCache[fmCacheCount * 4 + 2] = fm.ascent; + fmCache[fmCacheCount * 4 + 3] = fm.descent; + fmCacheCount++; + + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; + } + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; + } + } - boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP; + int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth, + firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks, + lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); - if (w <= width || isSpaceOrTab) { - fitWidth = w; - fit = j + 1; + int[] breaks = lineBreaks.breaks; + float[] lineWidths = lineBreaks.widths; + boolean[] flags = lineBreaks.flags; - if (fmTop < fitTop) - fitTop = fmTop; - if (fmAscent < fitAscent) - fitAscent = fmAscent; - if (fmDescent > fitDescent) - fitDescent = fmDescent; - if (fmBottom > fitBottom) - fitBottom = fmBottom; - while (breakOpp[breakOppIndex] != -1 - && breakOpp[breakOppIndex] < j - paraStart + 1) { - breakOppIndex++; - } - boolean isLineBreak = breakOppIndex < breakOpp.length && - breakOpp[breakOppIndex] == j - paraStart + 1; - - if (isLineBreak) { - okWidth = w; - ok = j + 1; - - if (fitTop < okTop) - okTop = fitTop; - if (fitAscent < okAscent) - okAscent = fitAscent; - if (fitDescent > okDescent) - okDescent = fitDescent; - if (fitBottom > okBottom) - okBottom = fitBottom; - } - } else { - final boolean moreChars; - int endPos; - int above, below, top, bottom; - float currentTextWidth; - - if (ok != here) { - endPos = ok; - above = okAscent; - below = okDescent; - top = okTop; - bottom = okBottom; - currentTextWidth = okWidth; - moreChars = (j + 1 < spanEnd); - } else if (fit != here) { - endPos = fit; - above = fitAscent; - below = fitDescent; - top = fitTop; - bottom = fitBottom; - currentTextWidth = fitWidth; - moreChars = (j + 1 < spanEnd); - } else { - // must make progress, so take next character - endPos = here + 1; - // but to deal properly with clusters - // take all zero width characters following that - while (endPos < spanEnd && widths[endPos - paraStart] == 0) { - endPos++; - } - above = fmAscent; - below = fmDescent; - top = fmTop; - bottom = fmBottom; - currentTextWidth = widths[here - paraStart]; - moreChars = (endPos < spanEnd); - } + // here is the offset of the starting character of the line we are currently measuring + int here = paraStart; - v = out(source, here, endPos, - above, below, top, bottom, - v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji, - needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, - chs, widths, paraStart, ellipsize, ellipsizedWidth, - currentTextWidth, paint, moreChars); - - here = endPos; - j = here - 1; // restart j-span loop from here, compensating for the j++ - ok = fit = here; - w = 0; - fitAscent = fitDescent = fitTop = fitBottom = 0; - okAscent = okDescent = okTop = okBottom = 0; - - if (--firstWidthLineLimit <= 0) { - width = restWidth; - } + int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; + int fmCacheIndex = 0; + int spanEndCacheIndex = 0; + int breakIndex = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + // retrieve end of span + spanEnd = spanEndCache[spanEndCacheIndex++]; + + // retrieve cached metrics, order matches above + fm.top = fmCache[fmCacheIndex * 4 + 0]; + fm.bottom = fmCache[fmCacheIndex * 4 + 1]; + fm.ascent = fmCache[fmCacheIndex * 4 + 2]; + fm.descent = fmCache[fmCacheIndex * 4 + 3]; + fmCacheIndex++; + + if (fm.top < fmTop) { + fmTop = fm.top; + } + if (fm.ascent < fmAscent) { + fmAscent = fm.ascent; + } + if (fm.descent > fmDescent) { + fmDescent = fm.descent; + } + if (fm.bottom > fmBottom) { + fmBottom = fm.bottom; + } - if (here < spanStart) { - // The text was cut before the beginning of the current span range. - // Exit the span loop, and get spanStart to start over from here. - measured.setPos(here); - spanEnd = here; - break; - } + // skip breaks ending before current span range + while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { + breakIndex++; + } - if (mLineCount >= mMaximumVisibleLineCount) { - return; - } + while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { + int endPos = paraStart + breaks[breakIndex]; + + boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this? + + v = out(source, here, endPos, + fmAscent, fmDescent, fmTop, fmBottom, + v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex], + needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, + chs, widths, paraStart, ellipsize, ellipsizedWidth, + lineWidths[breakIndex], paint, moreChars); + + if (endPos < spanEnd) { + // preserve metrics for current span + fmTop = fm.top; + fmBottom = fm.bottom; + fmAscent = fm.ascent; + fmDescent = fm.descent; + } else { + fmTop = fmBottom = fmAscent = fmDescent = 0; } - } - } - if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) { - if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) { - paint.getFontMetricsInt(fm); + here = endPos; + breakIndex++; - fitTop = fm.top; - fitBottom = fm.bottom; - fitAscent = fm.ascent; - fitDescent = fm.descent; + if (mLineCount >= mMaximumVisibleLineCount) { + return; + } } - - // Log.e("text", "output rest " + here + " to " + end); - - v = out(source, - here, paraEnd, fitAscent, fitDescent, - fitTop, fitBottom, - v, - spacingmult, spacingadd, chooseHt, - chooseHtv, fm, hasTabOrEmoji, - needMultiply, chdirs, dir, easy, bufEnd, - includepad, trackpad, chs, - widths, paraStart, ellipsize, - ellipsizedWidth, w, paint, paraEnd != bufEnd); } - paraStart = paraEnd; - if (paraEnd == bufEnd) break; } @@ -482,7 +391,7 @@ public class StaticLayout extends Layout { mLineCount < mMaximumVisibleLineCount) { // Log.e("text", "output last " + bufEnd); - measured.setPara(source, bufStart, bufEnd, textDir); + measured.setPara(source, bufEnd, bufEnd, textDir); paint.getFontMetricsInt(fm); @@ -847,10 +756,15 @@ public class StaticLayout extends Layout { mMeasured = MeasuredText.recycle(mMeasured); } - // returns an array with terminal sentinel value -1 to indicate end - // this is so that arrays can be recycled instead of allocating new arrays - // every time - private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle); + // populates LineBreaks and returns the number of breaks found + // + // the arrays inside the LineBreaks objects are passed in as well + // to reduce the number of JNI calls in the common case where the + // arrays do not have to be resized + private static native int nComputeLineBreaks(String locale, char[] text, float[] widths, + int length, float firstWidth, int firstWidthLineCount, float restWidth, + int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle, + int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength); private int mLineCount; private int mTopPadding, mBottomPadding; @@ -878,18 +792,23 @@ public class StaticLayout extends Layout { private static final int TAB_INCREMENT = 20; // same as Layout, but that's private private static final char CHAR_NEW_LINE = '\n'; - private static final char CHAR_TAB = '\t'; - private static final char CHAR_SPACE = ' '; - private static final char CHAR_ZWSP = '\u200B'; private static final double EXTRA_ROUNDING = 0.5; - private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800; - private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF; - /* * This is reused across calls to generate() */ private MeasuredText mMeasured; private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); + + // This is used to return three arrays from a single JNI call when + // performing line breaking + private static class LineBreaks { + private static final int INITIAL_SIZE = 16; + public int[] breaks = new int[INITIAL_SIZE]; + public float[] widths = new float[INITIAL_SIZE]; + public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji + // breaks, widths, and flags should all have the same length + } + } |
