package org.robolectric.shadows;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Join;
import org.robolectric.util.ReflectionHelpers;

/**
 * Broken. This implementation is very specific to the application for which it was developed.
 * Todo: Reimplement. Consider using the same strategy of collecting a history of draw events
 * and providing methods for writing queries based on type, number, and order of events.
 */
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Canvas.class)
public class ShadowCanvas {
  private List<PathPaintHistoryEvent> pathPaintEvents = new ArrayList<>();
  private List<CirclePaintHistoryEvent> circlePaintEvents = new ArrayList<>();
  private List<ArcPaintHistoryEvent> arcPaintEvents = new ArrayList<>();
  private List<RectPaintHistoryEvent> rectPaintEvents = new ArrayList<>();
  private List<LinePaintHistoryEvent> linePaintEvents = new ArrayList<>();
  private List<OvalPaintHistoryEvent> ovalPaintEvents = new ArrayList<>();
  private List<TextHistoryEvent> drawnTextEventHistory = new ArrayList<>();
  private Paint drawnPaint;
  private Bitmap targetBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
  private float translateX;
  private float translateY;
  private float scaleX = 1;
  private float scaleY = 1;
  private int height;
  private int width;

  /**
   * Returns a textual representation of the appearance of the object.
   *
   * @param canvas the canvas to visualize
   * @return The textual representation of the appearance of the object.
   */
  public static String visualize(Canvas canvas) {
    ShadowCanvas shadowCanvas = Shadow.extract(canvas);
    return shadowCanvas.getDescription();
  }

  @Implementation
  protected void __constructor__(Bitmap bitmap) {
    this.targetBitmap = bitmap;
  }

  public void appendDescription(String s) {
    ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
    shadowBitmap.appendDescription(s);
  }

  public String getDescription() {
    ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
    return shadowBitmap.getDescription();
  }

  @Implementation
  protected void setBitmap(Bitmap bitmap) {
    targetBitmap = bitmap;
  }

  @Implementation
  protected void drawText(String text, float x, float y, Paint paint) {
    drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text));
  }

  @Implementation
  protected void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
    drawnTextEventHistory.add(
        new TextHistoryEvent(x, y, paint, text.subSequence(start, end).toString()));
  }

  @Implementation
  protected void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
    drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, new String(text, index, count)));
  }

  @Implementation
  protected void drawText(String text, int start, int end, float x, float y, Paint paint) {
    drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text.substring(start, end)));
  }

  @Implementation
  protected void translate(float x, float y) {
    this.translateX = x;
    this.translateY = y;
  }

  @Implementation
  protected void scale(float sx, float sy) {
    this.scaleX = sx;
    this.scaleY = sy;
  }

  @Implementation
  protected void scale(float sx, float sy, float px, float py) {
    this.scaleX = sx;
    this.scaleY = sy;
  }

  @Implementation
  protected void drawPaint(Paint paint) {
    drawnPaint = paint;
  }

  @Implementation
  protected void drawColor(int color) {
    appendDescription("draw color " + color);
  }

  @Implementation
  protected void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
    describeBitmap(bitmap, paint);

    int x = (int) (left + translateX);
    int y = (int) (top + translateY);
    if (x != 0 || y != 0) {
      appendDescription(" at (" + x + "," + y + ")");
  }

    if (scaleX != 1 && scaleY != 1) {
      appendDescription(" scaled by (" + scaleX + "," + scaleY + ")");
    }
  }

  @Implementation
  protected void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
    describeBitmap(bitmap, paint);

    StringBuilder descriptionBuilder = new StringBuilder();
    if (dst != null) {
      descriptionBuilder.append(" at (").append(dst.left).append(",").append(dst.top)
          .append(") with height=").append(dst.height()).append(" and width=").append(dst.width());
    }

    if (src != null) {
      descriptionBuilder.append( " taken from ").append(src.toString());
    }
    appendDescription(descriptionBuilder.toString());
  }

  @Implementation
  protected void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
    describeBitmap(bitmap, paint);

    StringBuilder descriptionBuilder = new StringBuilder();
    if (dst != null) {
      descriptionBuilder.append(" at (").append(dst.left).append(",").append(dst.top)
          .append(") with height=").append(dst.height()).append(" and width=").append(dst.width());
    }

    if (src != null) {
      descriptionBuilder.append( " taken from ").append(src.toString());
    }
    appendDescription(descriptionBuilder.toString());
  }

  @Implementation
  protected void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
    describeBitmap(bitmap, paint);

    ShadowMatrix shadowMatrix = Shadow.extract(matrix);
    appendDescription(" transformed by " + shadowMatrix.getDescription());
  }

  @Implementation
  protected void drawPath(Path path, Paint paint) {
    pathPaintEvents.add(new PathPaintHistoryEvent(new Path(path), new Paint(paint)));

    separateLines();
    ShadowPath shadowPath = Shadow.extract(path);
    appendDescription("Path " + shadowPath.getPoints().toString());
  }

  @Implementation
  protected void drawCircle(float cx, float cy, float radius, Paint paint) {
    circlePaintEvents.add(new CirclePaintHistoryEvent(cx, cy, radius, paint));
  }

  @Implementation
  protected void drawArc(
      RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
    arcPaintEvents.add(new ArcPaintHistoryEvent(oval, startAngle, sweepAngle, useCenter, paint));
  }

  @Implementation
  protected void drawRect(float left, float top, float right, float bottom, Paint paint) {
    rectPaintEvents.add(new RectPaintHistoryEvent(left, top, right, bottom, paint));
  }

  @Implementation
  protected void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
    linePaintEvents.add(new LinePaintHistoryEvent(startX, startY, stopX, stopY, paint));
  }

  @Implementation
  protected void drawOval(RectF oval, Paint paint) {
    ovalPaintEvents.add(new OvalPaintHistoryEvent(oval, paint));
  }

  @Implementation
  protected int save() {
    return 1;
  }

  @Implementation
  protected void restore() {}

  private void describeBitmap(Bitmap bitmap, Paint paint) {
    separateLines();

    ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
    appendDescription(shadowBitmap.getDescription());

    if (paint != null) {
      ColorFilter colorFilter = paint.getColorFilter();
      if (colorFilter != null) {
        if (colorFilter instanceof ColorMatrixColorFilter) {
          ColorMatrixColorFilter colorMatrixColorFilter = (ColorMatrixColorFilter) colorFilter;
          ShadowColorMatrixColorFilter shadowColorMatrixColorFilter =
              Shadow.extract(colorMatrixColorFilter);
          ColorMatrix colorMatrix = shadowColorMatrixColorFilter.getMatrix();
          appendDescription(" with ColorMatrixColorFilter<" + formatColorMatric(colorMatrix) + ">");
        } else {
          appendDescription(" with " + colorFilter);
        }
      }
    }
  }

  private String formatColorMatric(ColorMatrix colorMatrix) {
    List<String> floats = new ArrayList<>();
    for (float f : colorMatrix.getArray()) {
      String format = String.format("%.2f", f);
      format = format.replace(".00", "");
      floats.add(format);
    }
    return Join.join(",", floats);
  }

  private void separateLines() {
    if (getDescription().length() != 0) {
      appendDescription("\n");
    }
  }

  public int getPathPaintHistoryCount() {
    return pathPaintEvents.size();
  }

  public int getCirclePaintHistoryCount() {
    return circlePaintEvents.size();
  }

  public int getArcPaintHistoryCount() {
    return arcPaintEvents.size();
  }

  public boolean hasDrawnPath() {
    return getPathPaintHistoryCount() > 0;
  }

  public boolean hasDrawnCircle() {
    return circlePaintEvents.size() > 0;
  }

  public Paint getDrawnPathPaint(int i) {
    return pathPaintEvents.get(i).pathPaint;
  }

  public Path getDrawnPath(int i) {
    return pathPaintEvents.get(i).drawnPath;
  }

  public CirclePaintHistoryEvent getDrawnCircle(int i) {
    return circlePaintEvents.get(i);
  }

  public ArcPaintHistoryEvent getDrawnArc(int i) {
    return arcPaintEvents.get(i);
  }

  public void resetCanvasHistory() {
    drawnTextEventHistory.clear();
    pathPaintEvents.clear();
    circlePaintEvents.clear();
    rectPaintEvents.clear();
    linePaintEvents.clear();
    ovalPaintEvents.clear();
    ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
    shadowBitmap.setDescription("");
  }

  public Paint getDrawnPaint() {
    return drawnPaint;
  }

  public void setHeight(int height) {
    this.height = height;
  }

  public void setWidth(int width) {
    this.width = width;
  }

  @Implementation
  protected int getWidth() {
    return width;
  }

  @Implementation
  protected int getHeight() {
    return height;
  }

  public TextHistoryEvent getDrawnTextEvent(int i) {
    return drawnTextEventHistory.get(i);
  }

  public int getTextHistoryCount() {
    return drawnTextEventHistory.size();
  }

  public RectPaintHistoryEvent getDrawnRect(int i) {
    return rectPaintEvents.get(i);
  }

  public RectPaintHistoryEvent getLastDrawnRect() {
    return rectPaintEvents.get(rectPaintEvents.size() - 1);
  }

  public int getRectPaintHistoryCount() {
    return rectPaintEvents.size();
  }

  public LinePaintHistoryEvent getDrawnLine(int i) {
    return linePaintEvents.get(i);
  }

  public int getLinePaintHistoryCount() {
    return linePaintEvents.size();
  }

  public int getOvalPaintHistoryCount() {
    return ovalPaintEvents.size();
  }

  public OvalPaintHistoryEvent getDrawnOval(int i) {
    return ovalPaintEvents.get(i);
  }

  public static class LinePaintHistoryEvent {
    public Paint paint;
    public float startX;
    public float startY;
    public float stopX;
    public float stopY;

    private LinePaintHistoryEvent(
        float startX, float startY, float stopX, float stopY, Paint paint) {
      this.paint = new Paint(paint);
      this.paint.setColor(paint.getColor());
      this.paint.setStrokeWidth(paint.getStrokeWidth());
      this.startX = startX;
      this.startY = startY;
      this.stopX = stopX;
      this.stopY = stopY;
    }
  }

  public static class OvalPaintHistoryEvent {
    public final RectF oval;
    public final Paint paint;

    private OvalPaintHistoryEvent(RectF oval, Paint paint) {
      this.oval = new RectF(oval);
      this.paint = new Paint(paint);
      this.paint.setColor(paint.getColor());
      this.paint.setStrokeWidth(paint.getStrokeWidth());
    }
  }

  public static class RectPaintHistoryEvent {
    public final Paint paint;
    public final RectF rect;
    public final float left;
    public final float top;
    public final float right;
    public final float bottom;

    private RectPaintHistoryEvent(
        float left, float top, float right, float bottom, Paint paint){
      this.rect = new RectF(left, top, right, bottom);
      this.paint = new Paint(paint);
      this.paint.setColor(paint.getColor());
      this.paint.setStrokeWidth(paint.getStrokeWidth());
      this.paint.setTextSize(paint.getTextSize());
      this.paint.setStyle(paint.getStyle());
      this.left = left;
      this.top = top;
      this.right = right;
      this.bottom = bottom;
    }
  }

  private static class PathPaintHistoryEvent {
    private final Path drawnPath;
    private final Paint pathPaint;

    PathPaintHistoryEvent(Path drawnPath, Paint pathPaint) {
      this.drawnPath = drawnPath;
      this.pathPaint = pathPaint;
    }
  }

  public static class CirclePaintHistoryEvent {
    public final float centerX;
    public final float centerY;
    public final float radius;
    public final Paint paint;

    private CirclePaintHistoryEvent(float centerX, float centerY, float radius, Paint paint) {
      this.centerX = centerX;
      this.centerY = centerY;
      this.radius = radius;
      this.paint = paint;
    }
  }

  public static class ArcPaintHistoryEvent {
    public final RectF oval;
    public final float startAngle;
    public final float sweepAngle;
    public final boolean useCenter;
    public final Paint paint;

    public ArcPaintHistoryEvent(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
                                Paint paint) {
      this.oval = oval;
      this.startAngle = startAngle;
      this.sweepAngle = sweepAngle;
      this.useCenter = useCenter;
      this.paint = paint;
    }
  }

  public static class TextHistoryEvent {
    public final float x;
    public final float y;
    public final Paint paint;
    public final String text;

    private TextHistoryEvent(float x, float y, Paint paint, String text) {
      this.x = x;
      this.y = y;
      this.paint = paint;
      this.text = text;
    }
  }
}


package org.robolectric.shadows;

import android.os.SystemClock;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.LooperMode;

public class ShadowSystem {

  /**
   * Implements {@link System#nanoTime} through ShadowWrangler.
   *
   * @return Current time with nanos.
   */
  @SuppressWarnings("unused")
  public static long nanoTime() {
    if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
      return TimeUnit.MILLISECONDS.toNanos(SystemClock.uptimeMillis());
    } else {
      return ShadowLegacySystemClock.nanoTime();
    }
  }

  /**
   * Implements {@link System#currentTimeMillis} through ShadowWrangler.
   *
   * @return Current time with millis.
   */
  @SuppressWarnings("unused")
  public static long currentTimeMillis() {
    if (ShadowLooper.looperMode() == LooperMode.Mode.PAUSED) {
      return SystemClock.uptimeMillis();
    } else {
      return ShadowLegacySystemClock.currentTimeMillis();
    }
  }
}


package org.robolectric;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import static org.robolectric.shadows.ShadowLooper.assertLooperMode;

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import java.nio.file.Path;
import org.robolectric.android.Bootstrap;
import org.robolectric.android.ConfigurationV25;
import org.robolectric.res.ResourceTable;
import org.robolectric.util.Scheduler;
import org.robolectric.util.TempDirectory;

public class RuntimeEnvironment {
  public static Context systemContext;

  /**
   * @deprecated Please migrate to {@link
   *     androidx.test.core.app.ApplicationProvider#getApplicationContext}
   */
  @Deprecated public static Application application;

  private static volatile Thread mainThread;
  private static Object activityThread;
  private static int apiLevel;
  private static Scheduler masterScheduler;
  private static ResourceTable systemResourceTable;
  private static ResourceTable appResourceTable;
  private static ResourceTable compileTimeResourceTable;
  private static TempDirectory tempDirectory = new TempDirectory("no-test-yet");
  private static Path androidFrameworkJar;
  public static Path compileTimeSystemResourcesFile;

  private static boolean useLegacyResources;

  /**
   * Tests if the given thread is currently set as the main thread.
   *
   * @param thread the thread to test.
   * @return <tt>true</tt> if the specified thread is the main thread, <tt>false</tt> otherwise.
   * @see #isMainThread()
   */
  public static boolean isMainThread(Thread thread) {
    assertLooperMode(LEGACY);
    return thread == mainThread;
  }

  /**
   * Tests if the current thread is currently set as the main thread.
   *
   * Not supported in realistic looper mode.
   *
   * @return <tt>true</tt> if the current thread is the main thread, <tt>false</tt> otherwise.
   */
  public static boolean isMainThread() {
    assertLooperMode(LEGACY);
    return isMainThread(Thread.currentThread());
  }

  /**
   * Retrieves the main thread. The main thread is the thread to which the main looper is attached.
   * Defaults to the thread that initialises the <tt>RuntimeEnvironment</tt> class.
   *
   * Not supported in realistic looper mode.
   *
   * @return The main thread.
   * @see #setMainThread(Thread)
   * @see #isMainThread()
   */
  public static Thread getMainThread() {
    assertLooperMode(LEGACY);
    return mainThread;
  }

  /**
   * Sets the main thread. The main thread is the thread to which the main looper is attached.
   * Defaults to the thread that initialises the <tt>RuntimeEnvironment</tt> class.
   *
   * Not supported in realistic looper mode.
   *
   * @param newMainThread the new main thread.
   * @see #setMainThread(Thread)
   * @see #isMainThread()
   */
  public static void setMainThread(Thread newMainThread) {
    assertLooperMode(LEGACY);
    mainThread = newMainThread;
  }

  public static Object getActivityThread() {
    return activityThread;
  }

  public static void setActivityThread(Object newActivityThread) {
    activityThread = newActivityThread;
  }

  /**
   * Returns a qualifier string describing the current {@link Configuration} of the system resources.
   *
   * @return a qualifier string as described (https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules)[here].
   */
  public static String getQualifiers() {
    Resources systemResources = Resources.getSystem();
    return getQualifiers(systemResources.getConfiguration(), systemResources.getDisplayMetrics());
  }

  /**
   * Returns a qualifier string describing the given configuration and display metrics.
   *
   * @param configuration the configuration.
   * @param displayMetrics the display metrics.
   * @return a qualifier string as described (https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules)[here].
   */
  public static String getQualifiers(Configuration configuration, DisplayMetrics displayMetrics) {
    return ConfigurationV25.resourceQualifierString(configuration, displayMetrics);
  }

  /**
   * Overrides the current device configuration.
   *
   * If `newQualifiers` starts with a plus (`+`), the prior configuration is used as the base
   * configuration, with the given changes applied additively. Otherwise, default values are used
   * for unspecified properties, as described [here](http://robolectric.org/device-configuration/).
   *
   * @param newQualifiers the qualifiers to apply
   */
  public static void setQualifiers(String newQualifiers) {
    Configuration configuration;
    DisplayMetrics displayMetrics = new DisplayMetrics();
    if (newQualifiers.startsWith("+")) {
      configuration = new Configuration(Resources.getSystem().getConfiguration());
      displayMetrics.setTo(Resources.getSystem().getDisplayMetrics());
    } else {
      configuration = new Configuration();
    }
    Bootstrap.applyQualifiers(newQualifiers, getApiLevel(), configuration, displayMetrics);

    Resources systemResources = Resources.getSystem();
    systemResources.updateConfiguration(configuration, displayMetrics);

    if (application != null) {
      application.getResources().updateConfiguration(configuration, displayMetrics);
    }
  }

  public static int getApiLevel() {
    return apiLevel;
  }

  public static Number castNativePtr(long ptr) {
    // Weird, using a ternary here doesn't work, there's some auto promotion of boxed types happening.
    if (getApiLevel() >= LOLLIPOP) {
      return ptr;
    } else {
      return (int) ptr;
    }
  }

  /**
   * Retrieves the current master scheduler. This scheduler is always used by the main
   * {@link android.os.Looper Looper}, and if the global scheduler option is set it is also used for
   * the background scheduler and for all other {@link android.os.Looper Looper}s
   *
   * @return The current master scheduler.
   * @see #setMasterScheduler(Scheduler)
   * see org.robolectric.Robolectric#getForegroundThreadScheduler()
   * see org.robolectric.Robolectric#getBackgroundThreadScheduler()
   */
  public static Scheduler getMasterScheduler() {
    return masterScheduler;
  }

  /**
   * Sets the current master scheduler. See {@link #getMasterScheduler()} for details.
   * Note that this method is primarily intended to be called by the Robolectric core setup code.
   * Changing the master scheduler during a test will have unpredictable results.
   *
   * @param masterScheduler the new master scheduler.
   * @see #getMasterScheduler()
   * see org.robolectric.Robolectric#getForegroundThreadScheduler()
   * see org.robolectric.Robolectric#getBackgroundThreadScheduler()
   */
  public static void setMasterScheduler(Scheduler masterScheduler) {
    RuntimeEnvironment.masterScheduler = masterScheduler;
  }

  public static void setSystemResourceTable(ResourceTable systemResourceTable) {
    RuntimeEnvironment.systemResourceTable = systemResourceTable;
  }

  public static void setAppResourceTable(ResourceTable appResourceTable) {
    RuntimeEnvironment.appResourceTable = appResourceTable;
  }

  public static ResourceTable getSystemResourceTable() {
    return systemResourceTable;
  }

  public static ResourceTable getAppResourceTable() {
    return appResourceTable;
  }

  public static void setCompileTimeResourceTable(ResourceTable compileTimeResourceTable) {
    RuntimeEnvironment.compileTimeResourceTable = compileTimeResourceTable;
  }

  public static ResourceTable getCompileTimeResourceTable() {
    return compileTimeResourceTable;
  }

  public static void setTempDirectory(TempDirectory tempDirectory) {
    RuntimeEnvironment.tempDirectory = tempDirectory;
  }

  public static TempDirectory getTempDirectory() {
    return tempDirectory;
  }

  public static void setAndroidFrameworkJarPath(Path localArtifactPath) {
    RuntimeEnvironment.androidFrameworkJar = localArtifactPath;
  }

  public static Path getAndroidFrameworkJarPath() {
    return RuntimeEnvironment.androidFrameworkJar;
  }

  /**
   * Internal only.
   *
   * @deprecated Do not use.
   */
  @Deprecated
  public static boolean useLegacyResources() {
    return useLegacyResources;
  }

  /**
   * Internal only.
   *
   * @deprecated Do not use.
   */
  @Deprecated
  public static void setUseLegacyResources(boolean useLegacyResources) {
    RuntimeEnvironment.useLegacyResources = useLegacyResources;
  }
}


/*
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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 org.robolectric.res.android;

import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.UnsignedBytes;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

/** Provides utilities to decode/encode a String packed in an arsc resource file. */
public final class ResourceString {

  /** Type of {@link ResourceString} to encode / decode. */
  public enum Type {
    UTF8(UTF_8),
    UTF16(UTF_16LE);

    private final Charset charset;

    Type(Charset charset) {
      this.charset = charset;
    }

    public Charset charset() {
      return charset;
    }
  }

  private ResourceString() {} // Private constructor

  /**
   * Given a buffer and an offset into the buffer, returns a String. The {@code offset} is the
   * 0-based byte offset from the start of the buffer where the string resides. This should be the
   * location in memory where the string's character count, followed by its byte count, and then
   * followed by the actual string is located.
   *
   *
Here's an example UTF-8-encoded string of ab©:
   *

   * 03 04 61 62 C2 A9 00
   * ^ Offset should be here
   *

   *
   * @param buffer The buffer containing the string to decode.
   * @param offset Offset into the buffer where the string resides.
   * @param type The encoding type that the {@link ResourceString} is encoded in.
   * @return The decoded string.
   */
  public static String decodeString(ByteBuffer buffer, int offset, Type type) {
    int length;
    int characterCount = decodeLength(buffer, offset, type);
    offset += computeLengthOffset(characterCount, type);
    // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length.
    // UTF-16 strings, however, only have 1 length: the number of characters.
    if (type == Type.UTF8) {
      length = decodeLength(buffer, offset, type);
      offset += computeLengthOffset(length, type);
    } else {
      length = characterCount * 2;
    }
    return new String(buffer.array(), offset, length, type.charset());
  }

  /**
   * Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string.
   * Strings are prefixed by 2 values. The first is the number of characters in the string.
   * The second is the encoding length (number of bytes in the string).
   *
   *
Here's an example UTF-8-encoded string of ab©:
   *
03 04 61 62 C2 A9 00

   *
   * @param str The string to be encoded.
   * @param type The encoding type that the {@link ResourceString} should be encoded in.
   * @return The encoded string.
   */
  public static byte[] encodeString(String str, Type type) {
    byte[] bytes = str.getBytes(type.charset());
    // The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator.
    ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5);
    encodeLength(output, str.length(), type);
    if (type == Type.UTF8) {  // Only UTF-8 strings have the encoding length.
      encodeLength(output, bytes.length, type);
    }
    output.write(bytes);
    // NULL-terminate the string
    if (type == Type.UTF8) {
      output.write(0);
    } else {
      output.writeShort(0);
    }
    return output.toByteArray();
  }

  /**
   * Builds a string from a null-terminated char data.
   */
  public static String buildString(char[] data) {
    int count = 0;
    for (count=0; count < data.length; count++) {
      if (data[count] == 0) {
        break;
      }
    }
    return new String(data, 0, count);
  }

  private static void encodeLength(ByteArrayDataOutput output, int length, Type type) {
    if (length < 0) {
      output.write(0);
      return;
    }
    if (type == Type.UTF8) {
      if (length > 0x7F) {
        output.write(((length & 0x7F00) >> 8) | 0x80);
      }
      output.write(length & 0xFF);
    } else {  // UTF-16
      // TODO(acornwall): Replace output with a little-endian output.
      if (length > 0x7FFF) {
        int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000;
        output.write(highBytes & 0xFF);
        output.write((highBytes & 0xFF00) >> 8);
      }
      int lowBytes = length & 0xFFFF;
      output.write(lowBytes & 0xFF);
      output.write((lowBytes & 0xFF00) >> 8);
    }
  }

  static int computeLengthOffset(int length, Type type) {
    return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1);
  }

  static int decodeLength(ByteBuffer buffer, int offset, Type type) {
    return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset);
  }

  static int decodeLengthUTF8(ByteBuffer buffer, int offset) {
    // UTF-8 strings use a clever variant of the 7-bit integer for packing the string length.
    // If the first byte is >= 0x80, then a second byte follows. For these values, the length
    // is WORD-length in big-endian & 0x7FFF.
    int length = UnsignedBytes.toInt(buffer.get(offset));
    if ((length & 0x80) != 0) {
      length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1));
    }
    return length;
  }

  static int decodeLengthUTF16(ByteBuffer buffer, int offset) {
    // UTF-16 strings use a clever variant of the 7-bit integer for packing the string length.
    // If the first word is >= 0x8000, then a second word follows. For these values, the length
    // is DWORD-length in big-endian & 0x7FFFFFFF.
    int length = (buffer.getShort(offset) & 0xFFFF);
    if ((length & 0x8000) != 0) {
      length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF);
    }
    return length;
  }
}


/*
 * Adapted from Android 9.0's ServiceLoader.
 *
 * Copyright (C) 2014 The Android Open Source Project
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.robolectric.util.inject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.ServiceConfigurationError;

/**
 * A simple service-provider locating facility.
 *
 * ServiceFinder is adapted from Android 9's {@link java.util.ServiceLoader}, the sole difference
 * being that rather than returning instances of the requested service, it returns the registered
 * provider classes without instantiating them.
 *
 * @see java.util.ServiceLoader
 */
@SuppressWarnings("NewApi")
final class ServiceFinder<S>
    implements Iterable<Class<? extends S>>
{

  private static final String PREFIX = "META-INF/services/";

  // The class or interface representing the service being loaded
  private final Class<S> service;

  // The class loader used to locate, load, and instantiate providers
  private final ClassLoader loader;

  // The access control context taken when the ServiceFinder is created
  // Android-changed: do not use legacy security code.
  // private final AccessControlContext acc;

  // Cached providers, in instantiation order
  private LinkedHashMap<String,Class<S>> providers = new LinkedHashMap<>();

  // The current lazy-lookup iterator
  private LazyIterator lookupIterator;

  /**
   * Clear this loader's provider cache so that all providers will be
   * reloaded.
   *
   *
 After invoking this method, subsequent invocations of the {@link
   * #iterator() iterator} method will lazily look up and instantiate
   * providers from scratch, just as is done by a newly-created loader.
   *
   *
 This method is intended for use in situations in which new providers
   * can be installed into a running Java virtual machine.
   */
  public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
  }

  private ServiceFinder(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    // Android-changed: Do not use legacy security code.
    // On Android, System.getSecurityManager() is always null.
    // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
  }

  private static void fail(Class<?> service, String msg, Throwable cause)
      throws ServiceConfigurationError
  {
    throw new ServiceConfigurationError(service.getName() + ": " + msg,
        cause);
  }

  private static void fail(Class<?> service, String msg)
      throws ServiceConfigurationError
  {
    throw new ServiceConfigurationError(service.getName() + ": " + msg);
  }

  private static void fail(Class<?> service, URL u, int line, String msg)
      throws ServiceConfigurationError
  {
    fail(service, u + ":" + line + ": " + msg);
  }

  // Parse a single line from the given configuration file, adding the name
  // on the line to the names list.
  //
  private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
      List<String> names)
      throws IOException, ServiceConfigurationError
  {
    String ln = r.readLine();
    if (ln == null) {
      return -1;
    }
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
      if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
        fail(service, u, lc, "Illegal configuration-file syntax");
      int cp = ln.codePointAt(0);
      if (!Character.isJavaIdentifierStart(cp))
        fail(service, u, lc, "Illegal provider-class name: " + ln);
      for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
        cp = ln.codePointAt(i);
        if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
          fail(service, u, lc, "Illegal provider-class name: " + ln);
      }
      if (!providers.containsKey(ln) && !names.contains(ln))
        names.add(ln);
    }
    return lc + 1;
  }

  // Parse the content of the given URL as a provider-configuration file.
  //
  // @param  service
  //         The service type for which providers are being sought;
  //         used to construct error detail strings
  //
  // @param  u
  //         The URL naming the configuration file to be parsed
  //
  // @return A (possibly empty) iterator that will yield the provider-class
  //         names in the given configuration file that are not yet members
  //         of the returned set
  //
  // @throws ServiceConfigurationError
  //         If an I/O error occurs while reading from the given URL, or
  //         if a configuration-file format error is detected
  //
  private Iterator<String> parse(Class<?> service, URL u)
      throws ServiceConfigurationError
  {
    InputStream in = null;
    BufferedReader r = null;
    ArrayList<String> names = new ArrayList<>();
    try {
      in = u.openStream();
      r = new BufferedReader(new InputStreamReader(in, "utf-8"));
      int lc = 1;
      while ((lc = parseLine(service, u, r, lc, names)) >= 0);
    } catch (IOException x) {
      fail(service, "Error reading configuration file", x);
    } finally {
      try {
        if (r != null) r.close();
        if (in != null) in.close();
      } catch (IOException y) {
        fail(service, "Error closing configuration file", y);
      }
    }
    return names.iterator();
  }

  // Private inner class implementing fully-lazy provider lookup
  //
  private class LazyIterator
      implements Iterator<Class<S>>
  {

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
      this.service = service;
      this.loader = loader;
    }

    private boolean hasNextService() {
      if (nextName != null) {
        return true;
      }
      if (configs == null) {
        try {
          String fullName = PREFIX + service.getName();
          if (loader == null)
            configs = ClassLoader.getSystemResources(fullName);
          else
            configs = loader.getResources(fullName);
        } catch (IOException x) {
          fail(service, "Error locating configuration files", x);
        }
      }
      while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
          return false;
        }
        pending = parse(service, configs.nextElement());
      }
      nextName = pending.next();
      return true;
    }

    private Class<S> nextService() {
      if (!hasNextService())
        throw new NoSuchElementException();
      String cn = nextName;
      nextName = null;
      Class<?> c = null;
      try {
        c = Class.forName(cn, false, loader);
      } catch (ClassNotFoundException x) {
        fail(service,
            // Android-changed: Let the ServiceConfigurationError have a cause.
            "Provider " + cn + " not found", x);
        // "Provider " + cn + " not found");
      }
      if (!service.isAssignableFrom(c)) {
        // Android-changed: Let the ServiceConfigurationError have a cause.
        ClassCastException cce = new ClassCastException(
            service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
        fail(service,
            "Provider " + cn  + " not a subtype", cce);
        // fail(service,
        //        "Provider " + cn  + " not a subtype");
      }
      try {
        // S p = service.cast(c.newInstance());
        // providers.put(cn, c);
        // return p;
        Class<S> clazz = (Class<S>) c;
        providers.put(cn, clazz);
        return clazz;
      } catch (Throwable x) {
        fail(service,
            "Provider " + cn + " could not be instantiated",
            x);
      }
      throw new Error();          // This cannot happen
    }

    @Override
    public boolean hasNext() {
      // Android-changed: do not use legacy security code
      /* if (acc == null) { */
      return hasNextService();
            /*
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
            */
    }

    @Override
    public Class<S> next() {
      // Android-changed: do not use legacy security code
      /* if (acc == null) { */
      return nextService();
            /*
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
            */
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }

  }

  /**
   * Lazily loads the available providers of this loader's service.
   *
   *
 The iterator returned by this method first yields all of the
   * elements of the provider cache, in instantiation order.  It then lazily
   * loads and instantiates any remaining providers, adding each one to the
   * cache in turn.
   *
   *
 To achieve laziness the actual work of parsing the available
   * provider-configuration files and instantiating providers must be done by
   * the iterator itself.  Its {@link java.util.Iterator#hasNext hasNext} and
   * {@link java.util.Iterator#next next} methods can therefore throw a
   * {@link ServiceConfigurationError} if a provider-configuration file
   * violates the specified format, or if it names a provider class that
   * cannot be found and instantiated, or if the result of instantiating the
   * class is not assignable to the service type, or if any other kind of
   * exception or error is thrown as the next provider is located and
   * instantiated.  To write robust code it is only necessary to catch {@link
   * ServiceConfigurationError} when using a service iterator.
   *
   *
 If such an error is thrown then subsequent invocations of the
   * iterator will make a best effort to locate and instantiate the next
   * available provider, but in general such recovery cannot be guaranteed.
   *
   *
Design Note
   * Throwing an error in these cases may seem extreme.  The rationale for
   * this behavior is that a malformed provider-configuration file, like a
   * malformed class file, indicates a serious problem with the way the Java
   * virtual machine is configured or is being used.  As such it is
   * preferable to throw an error rather than try to recover or, even worse,
   * fail silently.

   *
   *
 The iterator returned by this method does not support removal.
   * Invoking its {@link java.util.Iterator#remove() remove} method will
   * cause an {@link UnsupportedOperationException} to be thrown.
   *
   * @implNote When adding providers to the cache, the {@link #iterator
   * Iterator} processes resources in the order that the {@link
   * java.lang.ClassLoader#getResources(java.lang.String)
   * ClassLoader.getResources(String)} method finds the service configuration
   * files.
   *
   * @return  An iterator that lazily loads providers for this loader's
   *          service
   */
  @Override
  public Iterator<Class<? extends S>> iterator() {
    return new Iterator<Class<? extends S>>() {

      Iterator<Map.Entry<String,Class<S>>> knownProviders
          = providers.entrySet().iterator();

      @Override
      public boolean hasNext() {
        if (knownProviders.hasNext())
          return true;
        return lookupIterator.hasNext();
      }

      @Override
      public Class<S> next() {
        if (knownProviders.hasNext())
          return knownProviders.next().getValue();
        return lookupIterator.next();
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }

    };
  }

  /**
   * Creates a new service loader for the given service type and class
   * loader.
   *
   * @param  <S> the class of the service type
   *
   * @param  service
   *         The interface or abstract class representing the service
   *
   * @param  loader
   *         The class loader to be used to load provider-configuration files
   *         and provider classes, or <tt>null</tt> if the system class
   *         loader (or, failing that, the bootstrap class loader) is to be
   *         used
   *
   * @return A new service loader
   */
  public static <S> ServiceFinder<S> load(Class<S> service,
      ClassLoader loader)
  {
    return new ServiceFinder<>(service, loader);
  }

  /**
   * Creates a new service loader for the given service type, using the
   * current thread's {@linkplain java.lang.Thread#getContextClassLoader
   * context class loader}.
   *
   *
 An invocation of this convenience method of the form
   *
   *

   * ServiceFinder.load(service)

   *
   * is equivalent to
   *
   *

   * ServiceFinder.load(service,
   *                    Thread.currentThread().getContextClassLoader())

   *
   * @param  <S> the class of the service type
   *
   * @param  service
   *         The interface or abstract class representing the service
   *
   * @return A new service loader
   */
  public static <S> ServiceFinder<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return load(service, cl);
  }

  /**
   * Creates a new service loader for the given service type, using the
   * extension class loader.
   *
   *
 This convenience method simply locates the extension class loader,
   * call it <tt>extClassLoader</tt>, and then returns
   *
   *

   * ServiceFinder.load(service, extClassLoader)

   *
   *
 If the extension class loader cannot be found then the system class
   * loader is used; if there is no system class loader then the bootstrap
   * class loader is used.
   *
   *
 This method is intended for use when only installed providers are
   * desired.  The resulting service will only find and load providers that
   * have been installed into the current Java virtual machine; providers on
   * the application's class path will be ignored.
   *
   * @param  <S> the class of the service type
   *
   * @param  service
   *         The interface or abstract class representing the service
   *
   * @return A new service loader
   */
  public static <S> ServiceFinder<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
      prev = cl;
      cl = cl.getParent();
    }
    return load(service, prev);
  }

  // BEGIN Android-added: loadFromSystemProperty(), for internal use.
  // Instantiates a class from a system property (used elsewhere in libcore).
  /**
   * Internal API to support built-in SPIs that check a system property first.
   * Returns an instance specified by a property with the class' binary name, or null if
   * no such property is set.
   * @hide
   */
  public static <S> S loadFromSystemProperty(final Class<S> service) {
    try {
      final String className = System.getProperty(service.getName());
      if (className != null) {
        Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
        return (S) c.newInstance();
      }
      return null;
    } catch (Exception e) {
      throw new Error(e);
    }
  }
  // END Android-added: loadFromSystemProperty(), for internal use.

  /**
   * Returns a string describing this service.
   *
   * @return  A descriptive string
   */
  @Override
  public String toString() {
    return ServiceFinder.class.getName() + "[" + service.getName() + "]";
  }

}


package org.robolectric.plugins;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import javax.annotation.Nonnull;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.pluginapi.config.Configurer;

/**
 * Provides cached access to `robolectric-properties` files, for all your configuration needs!
 *
 * Used by {@link ConfigConfigurer} to support package configuration (see [Configuring
 * Robolectric](http://robolectric.org/configuring/) but it may be useful for other
 * {@link Configurer}s as well.
 */
@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
public class PackagePropertiesLoader {

  /**
   * We should get very high cache hit rates even with a tiny cache if we're called sequentially
   * by multiple {@link Configurer}s for the same package.
   */
  private final Map<String, Properties> cache = new LinkedHashMap<String, Properties>() {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Properties> eldest) {
      return size() > 3;
    }
  };

  /**
   * Return a {@link Properties} file for the given package name, or {@code null} if none is
   * available.
   *
   * @since 3.2
   */
  public Properties getConfigProperties(@Nonnull String packageName) {
    return cache.computeIfAbsent(packageName, s -> {
      StringBuilder buf = new StringBuilder();
      if (!packageName.isEmpty()) {
        buf.append(packageName.replace('.', '/'));
        buf.append('/');
      }
      buf.append(RobolectricTestRunner.CONFIG_PROPERTIES);
      final String resourceName = buf.toString();

      try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
        if (resourceAsStream == null) {
          return null;
        }
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        return properties;
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    });
  }

  // visible for testing
  InputStream getResourceAsStream(String resourceName) {
    return getClass().getClassLoader().getResourceAsStream(resourceName);
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
import static org.robolectric.RuntimeEnvironment.application;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.ReflectionHelpers.callConstructor;
import static org.robolectric.util.ReflectionHelpers.getStaticField;

import android.content.Intent;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import com.google.common.base.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.reflector.ForType;

/** Robolectric implementation of {@link android.hardware.usb.UsbManager}. */
@Implements(value = UsbManager.class, looseSignatures = true)
public class ShadowUsbManager {

  @RealObject
  private UsbManager realUsbManager;

  /**
   * A mapping from the package names to a list of USB devices for which permissions are granted.
   */
  private final HashMap<String, List<UsbDevice>> grantedPermissions = new HashMap<>();

  /**
   * A mapping from the USB device names to the USB device instances.
   *
   * @see UsbManager#getDeviceList()
   */
  private final HashMap<String, UsbDevice> usbDevices = new HashMap<>();

  /** A mapping from USB port to the status of that port. */
  private final HashMap<UsbPort, UsbPortStatus> usbPorts = new HashMap<>();

  private UsbAccessory attachedUsbAccessory = null;

  /** Returns true if the caller has permission to access the device. */
  @Implementation
  protected boolean hasPermission(UsbDevice device) {
    return hasPermissionForPackage(device, RuntimeEnvironment.application.getPackageName());
  }

  /** Returns true if the given package has permission to access the device. */
  public boolean hasPermissionForPackage(UsbDevice device, String packageName) {
    List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
    return usbDevices != null && usbDevices.contains(device);
  }

  @Implementation(minSdk = N)
  @HiddenApi
  protected void grantPermission(UsbDevice device) {
    grantPermission(device, RuntimeEnvironment.application.getPackageName());
  }

  @Implementation(minSdk = N_MR1)
  @HiddenApi // SystemApi
  protected void grantPermission(UsbDevice device, String packageName) {
    List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
    if (usbDevices == null) {
      usbDevices = new ArrayList<>();
      grantedPermissions.put(packageName, usbDevices);
    }
    usbDevices.add(device);
  }

  /**
   * Revokes permission to a USB device granted to a package. This method does nothing if the
   * package doesn't have permission to access the device.
   */
  public void revokePermission(UsbDevice device, String packageName) {
    List<UsbDevice> usbDevices = grantedPermissions.get(packageName);
    if (usbDevices != null) {
      usbDevices.remove(device);
    }
  }

  /**
   * Returns a HashMap containing all USB devices currently attached. USB device name is the key for
   * the returned HashMap. The result will be empty if no devices are attached, or if USB host mode
   * is inactive or unsupported.
   */
  @Implementation
  protected HashMap<String, UsbDevice> getDeviceList() {
    return new HashMap<>(usbDevices);
  }

  @Implementation
  protected UsbAccessory[] getAccessoryList() {
    // Currently Android only supports having a single accessory attached, and if nothing
    // is attached, this method actually returns null in the real implementation.
    if (attachedUsbAccessory == null) {
      return null;
    }

    return new UsbAccessory[] {attachedUsbAccessory};
  }

  /** Sets the currently attached Usb accessory returned in #getAccessoryList. */
  public void setAttachedUsbAccessory(UsbAccessory usbAccessory) {
    this.attachedUsbAccessory = usbAccessory;
  }

  /**
   * Adds a USB device into available USB devices map with permission value. If the USB device
   * already exists, updates the USB device with new permission value.
   */
  public void addOrUpdateUsbDevice(UsbDevice usbDevice, boolean hasPermission) {
    Preconditions.checkNotNull(usbDevice);
    Preconditions.checkNotNull(usbDevice.getDeviceName());
    usbDevices.put(usbDevice.getDeviceName(), usbDevice);
    if (hasPermission) {
      grantPermission(usbDevice);
    } else {
      revokePermission(usbDevice, RuntimeEnvironment.application.getPackageName());
    }
  }

  /** Removes a USB device from available USB devices map. */
  public void removeUsbDevice(UsbDevice usbDevice) {
    Preconditions.checkNotNull(usbDevice);
    usbDevices.remove(usbDevice.getDeviceName());
    revokePermission(usbDevice, RuntimeEnvironment.application.getPackageName());
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected /* UsbPort[] */ Object getPorts() {
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      return new ArrayList<>(usbPorts.keySet());
    }

    return usbPorts.keySet().toArray(new UsbPort[usbPorts.size()]);
  }

  /** Remove all added ports from UsbManager. */
  public void clearPorts() {
    usbPorts.clear();
  }

  /** Adds a USB port to UsbManager. */
  public void addPort(String portId) {
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      usbPorts.put(
          (UsbPort) createUsbPort(realUsbManager, portId, UsbPortStatus.MODE_DUAL),
          (UsbPortStatus) createUsbPortStatus(
              UsbPortStatus.MODE_DUAL,
              UsbPortStatus.POWER_ROLE_SINK,
              UsbPortStatus.DATA_ROLE_DEVICE,
              0));
      return;
    }

    usbPorts.put(
        callConstructor(UsbPort.class,
            from(String.class, portId),
            from(int.class, getStaticField(UsbPort.class, "MODE_DUAL"))),
        (UsbPortStatus) createUsbPortStatus(
            getStaticField(UsbPort.class, "MODE_DUAL"),
            getStaticField(UsbPort.class, "POWER_ROLE_SINK"),
            getStaticField(UsbPort.class, "DATA_ROLE_DEVICE"),
            0));
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected /* UsbPortStatus */ Object getPortStatus(/* UsbPort */ Object port) {
    return usbPorts.get(port);
  }

  @Implementation(minSdk = M)
  @HiddenApi
  protected void setPortRoles(
      /* UsbPort */ Object port, /* int */ Object powerRole, /* int */ Object dataRole) {
    UsbPortStatus status = usbPorts.get(port);
    usbPorts.put(
        (UsbPort) port,
        (UsbPortStatus) createUsbPortStatus(
            status.getCurrentMode(),
            (int) powerRole,
            (int) dataRole,
            status.getSupportedRoleCombinations()));
    application.sendBroadcast(new Intent(UsbManager.ACTION_USB_PORT_CHANGED));
  }

  /** Opens a file descriptor from a temporary file. */
  @Implementation
  protected ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
    try {
      File tmpUsbDir =
          RuntimeEnvironment.getTempDirectory().createIfNotExists("usb-accessory").toFile();
      return ParcelFileDescriptor.open(
          new File(tmpUsbDir, "usb-accessory-file"), ParcelFileDescriptor.MODE_READ_WRITE);
    } catch (FileNotFoundException error) {
      throw new RuntimeException("Error shadowing openAccessory", error);
    }
  }

  /**
   * Helper method for creating a {@link UsbPortStatus}.
   *
   * Returns Object to avoid referencing the API M+ UsbPortStatus when running on older platforms.
   */
  private static Object createUsbPortStatus(
      int currentMode,
      int currentPowerRole,
      int currentDataRole,
      int supportedRoleCombinations) {
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      return new UsbPortStatus(
          currentMode,
          currentPowerRole,
          currentDataRole,
          supportedRoleCombinations,
          0,
          0
      );
    }
    return callConstructor(UsbPortStatus.class,
        from(int.class, currentMode),
        from(int.class, currentPowerRole),
        from(int.class, currentDataRole),
        from(int.class, supportedRoleCombinations));

  }

  /**
   * Helper method for creating a {@link UsbPort}.
   *
   * Returns Object to avoid referencing the API M+ UsbPort when running on older platforms.
   */
  private static Object createUsbPort(
      UsbManager usbManager,
      String id,
      int supportedModes) {
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      return new UsbPort(
          usbManager,
          id,
          supportedModes,
          0,
          false,
          false
      );
    }
    return callConstructor(UsbPort.class,
        from(UsbManager.class, usbManager),
        from(String.class, id),
        from(int.class, supportedModes));
  }

  /** Accessor interface for {@link UsbManager}'s internals. */
  @ForType(UsbManager.class)
  public interface _UsbManager_ {

    UsbPort[] getPorts();

    UsbPortStatus getPortStatus(UsbPort port);

    void setPortRoles(UsbPort port, int powerRole, int dataRole);

  }

  /** Accessor interface for {@link UsbManager}'s internals (Q+). */
  @ForType(UsbManager.class)
  public interface _UsbManagerQ_ {

    List<UsbPort> getPorts();

  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.content.res.Configuration;
import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManagerImpl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import java.util.HashMap;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@Implements(value = WindowManagerImpl.class, isInAndroidSdk = false)
public class ShadowWindowManagerImpl extends ShadowWindowManager {

  private static Display defaultDisplayJB;

  /** internal only */
  public static void configureDefaultDisplayForJBOnly(
      Configuration configuration, DisplayMetrics displayMetrics) {
    Class<?> arg2Type = ReflectionHelpers.loadClass(ShadowWindowManagerImpl.class.getClassLoader(),
        "android.view.CompatibilityInfoHolder");

    defaultDisplayJB = ReflectionHelpers.callConstructor(Display.class,
        ClassParameter.from(int.class, 0),
        ClassParameter.from(arg2Type, null));
    ShadowDisplay shadowDisplay = Shadow.extract(defaultDisplayJB);
    shadowDisplay.configureForJBOnly(configuration, displayMetrics);
  }

  @RealObject
  WindowManagerImpl realObject;
  private static final Multimap<Integer, View> views = ArrayListMultimap.create();

  @Implementation
  public void addView(View view, android.view.ViewGroup.LayoutParams layoutParams) {
    views.put(realObject.getDefaultDisplay().getDisplayId(), view);
    // views.add(view);
    directlyOn(
        realObject,
        WindowManagerImpl.class,
        "addView",
        ClassParameter.from(View.class, view),
        ClassParameter.from(ViewGroup.LayoutParams.class, layoutParams));
  }

  @Implementation
  public void removeView(View view) {
    views.remove(realObject.getDefaultDisplay().getDisplayId(), view);
    directlyOn(realObject, WindowManagerImpl.class, "removeView",
        ClassParameter.from(View.class, view));
  }

  @Implementation
  protected void removeViewImmediate(View view) {
    views.remove(realObject.getDefaultDisplay().getDisplayId(), view);
    directlyOn(
        realObject,
        WindowManagerImpl.class,
        "removeViewImmediate",
        ClassParameter.from(View.class, view));
  }

  public List<View> getViews() {
    return ImmutableList.copyOf(views.get(realObject.getDefaultDisplay().getDisplayId()));
  }

  @Implementation(maxSdk = JELLY_BEAN)
  public Display getDefaultDisplay() {
    if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
      return directlyOn(realObject, WindowManagerImpl.class).getDefaultDisplay();
    } else {
      return defaultDisplayJB;
    }
  }

  @Implements(className = "android.view.WindowManagerImpl$CompatModeWrapper", maxSdk = JELLY_BEAN)
  public static class ShadowCompatModeWrapper {
    @Implementation(maxSdk = JELLY_BEAN)
    protected Display getDefaultDisplay() {
      return defaultDisplayJB;
    }

  }

  @Resetter
  public static void reset() {
    defaultDisplayJB = null;
    views.clear();
    if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN) {
      ReflectionHelpers.setStaticField(
          WindowManagerImpl.class,
          "sWindowManager",
          ReflectionHelpers.newInstance(WindowManagerImpl.class));
      HashMap windowManagers =
          ReflectionHelpers.getStaticField(WindowManagerImpl.class, "sCompatWindowManagers");
      windowManagers.clear();
    }
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.P;

import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.CancellationSignal;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

/** Provides testing APIs for {@link FingerprintManager} */
@SuppressWarnings("NewApi")
@Implements(FingerprintManager.class)
public class ShadowFingerprintManager {

  private static final String TAG = "ShadowFingerprintManager";

  private boolean isHardwareDetected;
  private CryptoObject pendingCryptoObject;
  private AuthenticationCallback pendingCallback;
  private List<Fingerprint> fingerprints = Collections.emptyList();

  /**
   * Simulates a successful fingerprint authentication. An authentication request must have been
   * issued with {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, AuthenticationCallback, Handler)} and not cancelled.
   */
  public void authenticationSucceeds() {
    if (pendingCallback == null) {
      throw new IllegalStateException("No active fingerprint authentication request.");
    }

    AuthenticationResult result;
    if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
      result = new AuthenticationResult(pendingCryptoObject, null, 0);
    } else {
      result = ReflectionHelpers.callConstructor(AuthenticationResult.class,
          ClassParameter.from(CryptoObject.class, pendingCryptoObject),
          ClassParameter.from(Fingerprint.class, null));
    }

    pendingCallback.onAuthenticationSucceeded(result);
  }

  /**
   * Simulates a failed fingerprint authentication. An authentication request must have been
   * issued with {@link FingerprintManager#authenticate(CryptoObject, CancellationSignal, int, AuthenticationCallback, Handler)} and not cancelled.
   */
  public void authenticationFails() {
    if (pendingCallback == null) {
      throw new IllegalStateException("No active fingerprint authentication request.");
    }

    pendingCallback.onAuthenticationFailed();
  }

  /**
   * Success or failure can be simulated with a subsequent call to {@link #authenticationSucceeds()}
   * or {@link #authenticationFails()}.
   */
  @Implementation(minSdk = M)
  protected void authenticate(
      CryptoObject crypto,
      CancellationSignal cancel,
      int flags,
      AuthenticationCallback callback,
      Handler handler) {
    if (callback == null) {
      throw new IllegalArgumentException("Must supply an authentication callback");
    }

    if (cancel != null) {
      if (cancel.isCanceled()) {
        Log.w(TAG, "authentication already canceled");
        return;
      } else {
        cancel.setOnCancelListener(() -> {
          this.pendingCallback = null;
          this.pendingCryptoObject = null;
        });
      }
    }

    this.pendingCryptoObject = crypto;
    this.pendingCallback = callback;
  }

  /**
   * Sets the return value of {@link FingerprintManager#hasEnrolledFingerprints()}.
   *
   * @deprecated use {@link #setDefaultFingerprints} instead.
   */
  @Deprecated
  public void setHasEnrolledFingerprints(boolean hasEnrolledFingerprints) {
    setDefaultFingerprints(hasEnrolledFingerprints ? 1 : 0);
  }

  /**
   * Returns {@code false} by default, or the value specified via
   * {@link #setHasEnrolledFingerprints(boolean)}.
   */
  @Implementation(minSdk = M)
  protected boolean hasEnrolledFingerprints() {
    return !fingerprints.isEmpty();
  }

  /**
   * @return lists of current fingerprint items, the list be set via {@link #setDefaultFingerprints}
   */
  @HiddenApi
  @Implementation(minSdk = M)
  protected List<Fingerprint> getEnrolledFingerprints() {
    return new ArrayList<>(fingerprints);
  }

  /**
   * @return Returns the finger ID for the given index.
   */
  public int getFingerprintId(int index) {
    return ReflectionHelpers.callInstanceMethod(
        getEnrolledFingerprints().get(index),
        RuntimeEnvironment.getApiLevel() > P ? "getBiometricId" : "getFingerId");
  }

  /**
   * Enrolls the given number of fingerprints, which will be returned in {@link
   * #getEnrolledFingerprints}.
   *
   * @param num the quantity of fingerprint item.
   */
  public void setDefaultFingerprints(int num) {
    setEnrolledFingerprints(
        IntStream.range(0, num)
            .mapToObj(
                i ->
                    new Fingerprint(
                        /* name= */ "Fingerprint " + i,
                        /* groupId= */ 0,
                        /* fingerId= */ i,
                        /* deviceId= */ 0))
            .toArray(Fingerprint[]::new));
  }

  private void setEnrolledFingerprints(Fingerprint... fingerprints) {
    this.fingerprints = Arrays.asList(fingerprints);
  }

  /**
   * Sets the return value of {@link FingerprintManager#isHardwareDetected()}.
   */
  public void setIsHardwareDetected(boolean isHardwareDetected) {
    this.isHardwareDetected = isHardwareDetected;
  }

  /**
   * @return `false` by default, or the value specified via {@link #setIsHardwareDetected(boolean)}
   */
  @Implementation(minSdk = M)
  protected boolean isHardwareDetected() {
    return this.isHardwareDetected;
  }
}


package org.robolectric.shadows;

import android.content.Context;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(Toast.class)
public class ShadowToast {
  private String text;
  private int duration;
  private int gravity;
  private int xOffset;
  private int yOffset;
  private View view;
  private boolean cancelled;

  @RealObject Toast toast;

  @Implementation
  protected void __constructor__(Context context) {}

  @Implementation
  protected static Toast makeText(Context context, int resId, int duration) {
    return makeText(context, context.getResources().getString(resId), duration);
  }

  @Implementation
  protected static Toast makeText(Context context, CharSequence text, int duration) {
    Toast toast = new Toast(context);
    toast.setDuration(duration);
    ShadowToast shadowToast = Shadow.extract(toast);
    shadowToast.text = text.toString();
    return toast;
  }

  @Implementation
  protected void show() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    shadowApplication.getShownToasts().add(toast);
  }

  @Implementation
  protected void setText(int resId) {
    this.text = RuntimeEnvironment.application.getString(resId);
  }

  @Implementation
  protected void setText(CharSequence text) {
    this.text = text.toString();
  }

  @Implementation
  protected void setView(View view) {
    this.view = view;
  }

  @Implementation
  protected View getView() {
    return view;
  }

  @Implementation
  protected void setGravity(int gravity, int xOffset, int yOffset) {
    this.gravity = gravity;
    this.xOffset = xOffset;
    this.yOffset = yOffset;
  }

  @Implementation
  protected int getGravity() {
    return gravity;
  }

  @Implementation
  protected int getXOffset() {
    return xOffset;
  }

  @Implementation
  protected int getYOffset() {
    return yOffset;
  }

  @Implementation
  protected void setDuration(int duration) {
    this.duration = duration;
  }

  @Implementation
  protected int getDuration() {
    return duration;
  }

  @Implementation
  protected void cancel() {
    cancelled = true;
  }

  public boolean isCancelled() {
    return cancelled;
  }

  /**
   * Discards the recorded {@code Toast}s. Shown toasts are automatically cleared between
   * tests. This method allows the user to discard recorded toasts during the test in order to make assertions clearer
   * e.g:
   *
   * <pre>
   *
   *   // Show a single toast
   *   myClass.showToast();
   *
   *   assertThat(ShadowToast.shownToastCount()).isEqualTo(1);
   *   ShadowToast.reset();
   *
   *    // Show another toast
   *   myClass.showToast();
   *
   *   assertThat(ShadowToast.shownToastCount()).isEqualTo(1);
   *
   * </pre>
   */
  public static void reset() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    shadowApplication.getShownToasts().clear();
  }

  /**
   * Returns the number of {@code Toast} requests that have been made during this test run
   * or since {@link #reset()} has been called.
   *
   * @return the number of {@code Toast} requests that have been made during this test run
   *         or since {@link #reset()} has been called.
   */
  public static int shownToastCount() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    return shadowApplication.getShownToasts().size();
  }

  /**
   * Returns whether or not a particular custom {@code Toast} has been shown.
   *
   * @param message the message to search for
   * @param layoutResourceIdToCheckForMessage
   *                the id of the resource that contains the toast messages
   * @return whether the {@code Toast} was requested
   */
  public static boolean showedCustomToast(CharSequence message, int layoutResourceIdToCheckForMessage) {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    for (Toast toast : shadowApplication.getShownToasts()) {
      String text = ((TextView) toast.getView().findViewById(layoutResourceIdToCheckForMessage)).getText().toString();
      if (text.equals(message.toString())) {
        return true;
      }
    }
    return false;
  }

  /**
   * query method that returns whether or not a particular {@code Toast} has been shown.
   *
   * @param message the message to search for
   * @return whether the {@code Toast} was requested
   */
  public static boolean showedToast(CharSequence message) {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    for (Toast toast : shadowApplication.getShownToasts()) {
      ShadowToast shadowToast = Shadow.extract(toast);
      String text = shadowToast.text;
      if (text != null && text.equals(message.toString())) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns the text of the most recently shown {@code Toast}.
   *
   * @return the text of the most recently shown {@code Toast}
   */
  public static String getTextOfLatestToast() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    List<Toast> shownToasts = shadowApplication.getShownToasts();
    if (shownToasts.isEmpty()) {
      return null;
    } else {
      Toast latestToast = shownToasts.get(shownToasts.size() - 1);
      ShadowToast shadowToast = Shadow.extract(latestToast);
      return shadowToast.text;
    }
  }

  /**
   * Returns the most recently shown {@code Toast}.
   *
   * @return the most recently shown {@code Toast}
   */
  public static Toast getLatestToast() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    List<Toast> shownToasts = shadowApplication.getShownToasts();
    return (shownToasts.size() == 0) ? null : shownToasts.get(shownToasts.size() - 1);
  }
}


package org.robolectric.shadows;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.View;
import android.widget.Adapter;
import android.widget.FrameLayout;
import com.android.internal.app.AlertController;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(AlertDialog.class)
public class ShadowAlertDialog extends ShadowDialog {
  @RealObject
  private AlertDialog realAlertDialog;

  private CharSequence[] items;
  private DialogInterface.OnClickListener clickListener;
  private boolean isMultiItem;
  private boolean isSingleItem;
  private DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener;
  private FrameLayout custom;

  /**
   * @return the most recently created {@code AlertDialog}, or null if none has been created during this test run
   */
  public static AlertDialog getLatestAlertDialog() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    ShadowAlertDialog dialog = shadowApplication.getLatestAlertDialog();
    return dialog == null ? null : dialog.realAlertDialog;
  }

  public FrameLayout getCustomView() {
    if (custom == null) {
      custom = new FrameLayout(context);
    }
    return custom;
  }

  /**
   * Resets the tracking of the most recently created {@code AlertDialog}
   */
  public static void reset() {
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    shadowApplication.setLatestAlertDialog(null);
  }

  /**
   * Simulates a click on the {@code Dialog} item indicated by {@code index}. Handles both multi- and single-choice dialogs, tracks which items are currently
   * checked and calls listeners appropriately.
   *
   * @param index the index of the item to click on
   */
  public void clickOnItem(int index) {
    ShadowListView shadowListView = Shadow.extract(realAlertDialog.getListView());
    shadowListView.performItemClick(index);
  }

  @Override public CharSequence getTitle() {
    return getShadowAlertController().getTitle();
  }

  /**
   * @return the items that are available to be clicked on
   */
  public CharSequence[] getItems() {
    Adapter adapter = getShadowAlertController().getAdapter();
    int count = adapter.getCount();
    CharSequence[] items = new CharSequence[count];
    for (int i = 0; i < items.length; i++) {
      items[i] = (CharSequence) adapter.getItem(i);
    }
    return items;
  }

  public Adapter getAdapter() {
    return getShadowAlertController().getAdapter();
  }

  /**
   * @return the message displayed in the dialog
   */
  public CharSequence getMessage() {
    return getShadowAlertController().getMessage();
  }

  @Override
  public void show() {
    super.show();
    ShadowApplication shadowApplication = Shadow.extract(RuntimeEnvironment.application);
    shadowApplication.setLatestAlertDialog(this);
  }

  /**
   * @return the view set with {@link AlertDialog.Builder#setView(View)}
   */
  public View getView() {
    return getShadowAlertController().getView();
  }

  /**
   * @return the icon set with {@link AlertDialog.Builder#setIcon(int)}
   */
  public int getIconId() {
    return getShadowAlertController().getIconId();
  }

  /**
   * @return return the view set with {@link AlertDialog.Builder#setCustomTitle(View)}
   */
  public View getCustomTitleView() {
    return getShadowAlertController().getCustomTitleView();
  }

  private ShadowAlertController getShadowAlertController() {
    AlertController alertController = ReflectionHelpers.getField(realAlertDialog, "mAlert");
    return (ShadowAlertController) Shadow.extract(alertController);
  }

  @Implements(AlertDialog.Builder.class)
  public static class ShadowBuilder {
  }
}